---
url: https://docs.ai-hub.bbv.ch/index.md
title: "Swiss AI Hub"
description: "The open AI platform you own and control"
---
# Swiss AI Hub
> The open AI platform you own and control
Complete infrastructure for production AI. Deploy in your data center. Build with confidence. Keep your data in Switzerland.
## Features
### The open-source AI bet
Best-in-class open-source tools (LiteLLM, Milvus, LlamaIndex) integrated and ready. When they evolve, you benefit. No vendor lock-in, no licensing fees, no platform constraints. Bet on the ecosystem, not a single vendor.
### Own the platform, not rent it
Complete infrastructure you deploy and control. Not SaaS subscriptions, not a code library. Authentication, monitoring, databases, UIs—everything included. Azure AI-level completeness with LangChain-level ownership.
### 30 minutes to production AI
One command deploys everything. LLM gateway, vector search, chat interface, authentication, monitoring. Pre-built agents work immediately. No cloud provisioning, no complex setup, no infrastructure engineering.
### One GPU, zero cloud dependency
Run the entire platform—chat, embeddings, reranking, OCR, speech—on a single NVIDIA RTX 6000 Pro. No API keys, no egress traffic, no cloud bills. Full AI capability in an air-gapped rack. When cloud access is available, Swiss LLM Cloud scales you further without code changes.
### Transparent, not black-box AI
Every agent follows explicit workflows. Every decision is traceable and auditable. Every cost is tracked. See exactly why AI gave an answer. Trust through transparency, not promises. Built for Swiss standards.
### Collective strength through collaboration
Swiss organizations sharing infrastructure, competing on innovation. Contribute improvements, benefit from others'. Build together what no single organization could afford. The Swiss AI advantage.
## FAQ
::: details How can our Swiss organization deploy AI while keeping all data on-premise?
The Swiss AI Hub is an **open-source AI platform** that *you* deploy and control. It's specifically designed for
**on-premise installation** within your own Swiss data center. This means you can run the entire AI infrastructure -
including language model gateways and knowledge databases - on your servers, ensuring complete control and data
isolation. You can find more details in our
[Deployment Options](docs/2_platform/3_deployment_guide/1_deployment_options/).
:::
::: details How can we use AI and comply with Swiss data protection laws like FADP (revDSG)?
Ensuring **Swiss data sovereignty** is a core principle of the Swiss AI Hub community. Because *you* deploy the
platform, you choose *where* it runs – either on your own servers in Switzerland or within trusted Swiss data centers.
By using local Large Language Models (LLMs), sensitive data processing stays entirely within your control, helping you
meet **FADP (revDSG) requirements**. Read about our commitment in
[The Swiss Way: Privacy, Sovereignty, and Transparency](docs/1_vision_and_positioning/1_introduction/3_the_swiss_way/).
:::
::: details What is a trustworthy, open-source AI platform alternative to large cloud vendors like Azure AI or Google Vertex AI?
The Swiss AI Hub offers an **open-source alternative** built by a community focused on user control. The platform
runtime is licensed under Apache 2.0 (the UI and backup orchestration under AGPL-3.0; see
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the full per-package breakdown), meaning
*you* own your deployment. This lets you avoid **vendor lock-in**, giving you freedom from specific ecosystems and
unpredictable pricing structures common with large cloud providers. See how we compare in the
[Comparison Matrix](docs/1_vision_and_positioning/2_why_swiss_ai_hub/1_comparison_matrix_light/).
:::
::: details How can we ensure AI decisions made within our Swiss company are traceable and auditable?
The Swiss AI Hub community prioritizes **transparency for trust**. Our platform provides deep observability features.
Every step an AI agent takes is visible, decisions are logged with context, and costs are tracked. Tools like Langfuse
allow tracing every interaction, so you can always understand *why* an AI provided a certain answer, which is crucial
for **compliance and auditing**. Explore these features under [Auditing & Observability](docs/2_platform/12_auditing/).
:::
::: details Is there a ready-made AI infrastructure stack (auth, monitoring, vector DBs) that we can deploy ourselves?
Yes, the Swiss AI Hub provides a **complete, pre-integrated AI infrastructure stack** that *you* deploy. It bundles
essential components like authentication, monitoring, various databases (including vector databases for AI), data
processing pipelines, and user interfaces right out of the box. This solves many common **production AI challenges**
from day one. Learn about this in
[The 'Day 2' Advantage](docs/1_vision_and_positioning/2_why_swiss_ai_hub/2_the_day_2_advantage/).
:::
::: details What is the quickest way to set up a secure, enterprise-ready AI platform within Switzerland?
You can deploy the entire Swiss AI Hub platform in about **30 minutes using a single command**. As an open-source
platform you install yourself, it includes pre-built agents and interfaces that start working immediately, offering
rapid time-to-value without complex cloud configurations. Get started with the
[Quick Start Guide](docs/2_platform/1_quick_start/).
:::
::: details How can our employees securely access company-specific AI help directly within Microsoft Teams or Slack?
The Swiss AI Hub platform includes **built-in integrations for Microsoft Teams and Slack**. This allows your employees
to interact securely with AI agents, which have access to relevant company knowledge, directly within the collaboration
tools they use every day, improving workflow. See details on
[Slack & Teams Integrations](docs/2_platform/17_slack_teams_integrations/).
:::
::: details How can our organization centrally manage access and usage of various AI models (e.g., GPT-4, Gemini, local models)?
The Swiss AI Hub includes an **integrated LLM Proxy (LiteLLM)** that acts as a unified gateway to all your configured AI
models. You can centrally manage model access, route requests based on policies, track costs across different providers,
and even set up failover mechanisms. More info is available under
[Language Models](docs/2_platform/13_language_models/).
:::
::: details How can we effectively control and predict the operational costs associated with using AI models?
Our community built the Swiss AI Hub with **transparent cost control** in mind. The integrated LLM Proxy tracks token
usage for every interaction, per user or agent. You can monitor AI spending in real-time dashboards and configure
budgets to prevent unexpected costs. Learn more about [Cost Control](docs/2_platform/14_cost_control/).
:::
::: details Our Swiss company has strict data privacy rules preventing the use of public AI clouds. What secure AI solution can we use?
The Swiss AI Hub is ideal for this. As an open-source platform *you* deploy, you can install it **fully on-premise** and
use **local, self-hosted LLMs**. This ensures that absolutely no data (prompts, responses, documents) ever leaves your
secure network perimeter. Review our comprehensive [Security features](docs/2_platform/20_security/).
:::
::: details We have AI prototypes using frameworks like LangChain but find deploying them reliably in production difficult. How can the Swiss AI Hub help?
The Swiss AI Hub provides the necessary **production-ready infrastructure** that development frameworks often lack.
While LangChain helps build the AI logic, our platform delivers the essential surrounding components: robust deployment
mechanisms, enterprise authentication, scaling, monitoring, and user interfaces needed for **reliable enterprise use**.
See [Our Solution](docs/1_vision_and_positioning/1_introduction/2_our_solution/).
:::
::: details How can we securely let AI agents answer questions based on our internal company documents (like PDFs or Word files)?
The Swiss AI Hub includes a secure **Retrieval-Augmented Generation (RAG) system**. You configure automated
[Data Pipelines](docs/2_platform/6_pipelines/) to ingest documents from your sources (like SharePoint). These pipelines
securely process the documents and index them in a vector database *that you own and control*, allowing agents to access
company knowledge safely.
:::
::: details Different teams in our organization are using various AI tools, creating silos. How can we create a unified, governed AI approach?
The Swiss AI Hub can serve as your **central, unified AI platform**. It provides common infrastructure that all teams
can build upon, ensures consistent governance and security policies, offers unified monitoring, and includes an
[OpenAI-Compatible API](docs/2_platform/18_api/1_openai_compatible_api/) allowing integration with many existing tools,
helping to **reduce fragmentation**.
:::
::: details What is an efficient and scalable way to handle the ingestion and vector embedding of thousands of company documents for AI?
The Swiss AI Hub utilizes **Data Pipelines**, built using the robust orchestrator Dagster. These pipelines automate the
entire workflow: connecting to your data sources, parsing various file formats intelligently, creating semantic chunks,
generating vector embeddings, and indexing them into your vector store (like Milvus). Find details in the
[Pipelines section](docs/2_platform/6_pipelines/).
:::
::: details Can we deploy and run a complete AI platform entirely offline in an air-gapped network within Switzerland?
Yes. When you deploy the Swiss AI Hub **on-premise** and configure it to use only **self-hosted Large Language Models**
(LLMs), the entire platform can operate without any external internet connection. This makes it suitable for
**air-gapped environments** with the highest security requirements. See
[Deployment Options](docs/2_platform/3_deployment_guide/1_deployment_options/).
:::
::: details How do we ensure the AI agents provide trustworthy answers and aren't just "hallucinating" or making things up?
Trust is paramount. Swiss AI Hub **Agents** are designed to follow explicit, defined workflows. They primarily use
**Retrieval-Augmented Generation (RAG)**, meaning their answers are based on information retrieved from *your* verified
company documents. Agents also **cite their sources**, and built-in "guardrails" check if the retrieved information is
sufficient, ensuring **reliable, fact-based responses**. Learn more about [Agents](docs/2_platform/5_agents/).
:::
::: details Is it possible for AI agents on this platform to ask for help or approval from human experts during complex tasks?
Yes, our platform supports **Human-in-the-Loop (HITL)** and **Bot-in-the-Loop (BITL)** workflows. An AI agent can be
designed to pause its process at a specific step, send a request for input or approval to a designated human expert (for
example, via a Slack message), and then seamlessly resume its work once the human responds. Explore
[Agent Fundamentals](docs/3_sdk/2_building_agents/1_agent_fundamentals/) which enable these patterns.
:::
::: details How does the Swiss AI Hub connect and integrate with our existing enterprise software like SharePoint or internal databases?
The platform offers **flexible integration options**. AI agents can make direct API calls to external systems; external
systems can trigger agents via the platform's [Agent Interaction API](docs/2_platform/18_api/2_agent_interaction_api/);
automated Data Pipelines can sync knowledge from sources like SharePoint; and standard protocols are supported for
custom connections. See [External Integrations](docs/2_platform/22_external_integrations/).
:::
::: details How does using an open-source platform like Swiss AI Hub benefit the broader Swiss AI community?
Our **ecosystem model** is built on collaboration. The core platform is open-source, allowing Swiss organizations to
pool efforts in building and improving the fundamental AI infrastructure. Everyone benefits from shared advancements,
letting individual organizations focus resources on creating unique AI applications specific to their needs,
strengthening Switzerland's overall AI capabilities. Read about
[The Ecosystem Model](docs/1_vision_and_positioning/2_why_swiss_ai_hub/3_the_ecosystem_model/).
:::
::: details We are concerned about the complexity of setting up and managing a self-hosted AI platform. Is the Swiss AI Hub difficult to operate?
The Swiss AI Hub community designed the platform for **simplified deployment and management**. The installation uses a
single command, and critical infrastructure challenges like authentication, monitoring, and scaling configuration are
solved out-of-the-box. This significantly reduces the operational complexity compared to building AI infrastructure from
scratch or managing intricate cloud vendor services. Check the [Quick Start](docs/2_platform/1_quick_start/).
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning.md
title: "Part I: Vision & Positioning"
description: ""
---
# Part I: Vision & positioning
The Swiss AI Hub represents a different approach to enterprise AI: a complete platform that you deploy, own, and
control. This section explains what we're building, why it matters, and how it changes the AI adoption landscape for
Swiss organizations.
You'll discover our vision for democratizing AI infrastructure, understand how we compare to existing solutions, and
learn about the collaborative ecosystem we're fostering. Whether you're evaluating AI platforms, planning your
organization's AI strategy, or looking to contribute to Swiss AI capabilities, this section provides the foundation for
understanding the Swiss AI Hub.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/1_introduction.md
title: "Introduction: The Swiss AI Hub Vision"
description: ""
---
# Introduction: The Swiss AI Hub vision
Building production AI systems requires solving two distinct challenges: creating intelligent capabilities and deploying
reliable infrastructure. Most organizations focus on the first while struggling with the second.
This chapter introduces the Swiss AI Hub's approach to this challenge. You'll understand:
- The gap between AI prototypes and production systems that every organization faces
- How the Swiss AI Hub provides complete infrastructure as a deployable product
- Why Swiss values of privacy, sovereignty, and transparency are embedded in every component
- The strategic separation between platform and SDK that enables both adoption and innovation
Start here to understand what the Swiss AI Hub is and the principles that guide its development.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/1_introduction/1_the_problem_we_solve.md
title: "The Problem We Solve"
description: ""
---
# The problem we solve
Building AI applications is easy. Building production AI systems is hard.
If you've worked with AI tools, you know this pattern: You can create an impressive demo with LangChain in an afternoon.
A week later, you have a working prototype. Then someone asks the hard questions:
- How do we deploy this?
- Where does our data stay?
- Can we track what the AI is doing?
- How do we control costs?
- What happens when it fails?
- How do users actually access it?
- Can we integrate it with our existing tools?
Suddenly, your elegant prototype needs authentication, monitoring, data pipelines, vector databases, cost controls,
audit trails, user interfaces, and enterprise integrations. You're not building an AI solution anymore - you're building
infrastructure.
## The infrastructure gap
Current AI development tools fall into two categories:
**Libraries and frameworks** like LangChain, LlamaIndex, and Semantic Kernel help you build agents quickly. They handle
the AI logic well, but they're just code libraries. You still need to figure out deployment, scaling, monitoring, and
everything else that makes software production-ready.
**Cloud AI services** like Azure AI Studio or Google Vertex AI provide infrastructure, but they lock you into their
ecosystem. Your data lives on their servers, you pay their margins forever, and you can't modify the platform when it
doesn't fit your needs.
Neither approach solves the actual problem enterprises face: **How do you go from AI experiments to production systems
without either building everything from scratch or surrendering control to a vendor?**
## The Swiss enterprise challenge
For Swiss organizations, these challenges are compounded by specific requirements:
::: warning Data sovereignty requirements
Swiss data protection laws and corporate policies often require that sensitive data remains within Swiss borders. Most
AI platforms can't guarantee this - they process data wherever their infrastructure runs.
:::
The typical journey looks like this:
1. **Experimentation blocked**: IT won't approve ChatGPT or Claude because data leaves the company
2. **Local attempts fail**: Teams try to run open-source models locally but lack the infrastructure
3. **Vendor evaluation stalls**: Enterprise AI platforms are expensive, complex, and still don't solve data residency
4. **Custom building overwhelms**: Building from scratch requires expertise the organization doesn't have
Organizations get stuck in a loop: They can't use existing solutions due to compliance, but they can't build their own
due to complexity.
## The real cost of fragmentation
When organizations do manage to deploy AI, they often end up with a fragmented landscape:
- **Team A** uses Azure OpenAI through Python scripts
- **Team B** built a RAG system with LlamaIndex that only they understand
- **Team C** has a chatbot that no one maintains anymore
- **Finance** wants cost tracking across all AI usage
- **IT** wants standardized deployment and monitoring
- **Compliance** wants audit trails and data governance
Each team solves their immediate problem but creates new ones. There's no shared infrastructure, no consistent
governance, no unified approach. The organization has AI capabilities but not an AI platform.
## What organizations actually need
The requirements are clear:
1. **Complete infrastructure** that handles deployment, monitoring, and scaling - not just AI logic
2. **Data sovereignty** with the option to run everything on-premise or in Swiss data centers
3. **Openness and control** to modify, extend, and integrate with existing systems
4. **Production readiness** with enterprise authentication, audit trails, and cost controls built in
5. **A unified platform** that different teams can build on without creating silos
This is the gap the Swiss AI Hub fills. Instead of choosing between building everything yourself or accepting vendor
lock-in, you get a complete platform that you own and control. One that's designed for the realities of enterprise AI
deployment, not just the excitement of AI development.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/1_introduction/2_our_solution.md
title: "Our solution"
description: ""
---
# Our solution: Enterprise AI infrastructure as a product
The Swiss AI Hub is a complete, open-source AI platform that you deploy, own, and control. It's not a service you
subscribe to or a framework you build upon - it's production-ready infrastructure that becomes yours.
## Platform + SDK: The complete package
The Swiss AI Hub consists of two complementary parts:
**The platform** is your AI infrastructure. Open-source (Apache 2.0 for the runtime + SDK, AGPL-3.0 for the UI and
backup orchestration; see [LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the
per-package breakdown), it includes everything needed to run AI in production: LLM gateway, vector databases, data
pipelines, authentication, monitoring, and user interfaces. Deploy it with `docker compose up` and you have a working AI
system.
**The SDK** is how you extend the platform. It provides the patterns, tools, and frameworks for building agents,
pipelines, and processes that automatically integrate with the platform. When you build with our SDK, your components
inherit all platform capabilities - they don't need custom deployment, monitoring, or user access because the platform
handles it.
## What you get out of the box
When you deploy the Swiss AI Hub, you immediately have:
::: details Infrastructure layer
- **Unified LLM gateway** through LiteLLM, connecting to any model provider
- **Vector databases** (Milvus) for semantic search and RAG
- **Document processing** with MinerU for PDFs, Office files, and more
- **Data pipelines** using Dagster for ingestion and processing
- **Message queuing** with NATS for event-driven architecture
- **Object storage** via SeaweedFS S3-compatible layer
- **Multiple databases** (PostgreSQL, FerretDB, ValKey) pre-configured
:::
::: details AI capabilities
- **Multi-provider LLM access** with automatic failover and cost tracking
- **Built-in RAG** with document parsing, chunking, and retrieval
- **Agent orchestration** for complex multi-step workflows
- **Process automation** coordinating between humans, AI, and systems
- **PII detection and anonymization** through Presidio
- **Embeddings and semantic search** with configurable models
:::
::: details Enterprise features
- **SSO/OAuth integration** with your identity provider
- **Role-based access control** with granular permissions
- **Full audit trails** for compliance and debugging
- **Cost tracking and limits** per user, team, or model
- **API tokens** for programmatic access
- **Langfuse tracing** for complete observability
:::
::: details User interfaces
- **Modern chat interface** with voice, images, and documents
- **Process cockpit** for workflow monitoring and participation
- **Admin dashboard** for system management
- **Microsoft Teams and Slack bots** for where users already work
- **OpenAI-compatible API** for existing tool integration
:::
## How it solves the infrastructure problem
Remember those hard questions from before? Here's how the platform answers them:
::: tip "How do we deploy this?"
Everything runs in containers. One command starts the entire stack. Scale by adjusting container counts.
:::
::: tip "Where does our data stay?"
Wherever you deploy it. Run on-premise, in a Swiss data center, or your preferred cloud. Your infrastructure, your
control.
:::
::: tip "Can we track what the AI is doing?"
Every agent action is traced through Langfuse. Every API call is logged. Every decision is auditable.
:::
::: tip "How do we control costs?"
LiteLLM provides unified cost tracking across all models. Set limits by user, team, or globally.
:::
::: tip "What happens when it fails?"
Built-in error handling, automatic failover between models, and graceful degradation to human review.
:::
::: tip "How do users actually access it?"
Through the web UI, Teams, Slack, or API. Authentication handled by your existing identity provider.
:::
::: tip "Can we integrate it with our existing tools?"
OpenAI-compatible API for tool compatibility. Event-driven architecture for custom integrations. Webhook endpoints for
external systems.
:::
## Why open source changes everything
The open-source license model (see [LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the
per-package breakdown) means you're not adopting a platform - you're acquiring one:
- **No vendor lock-in**: The code is yours. Run it anywhere, modify it as needed
- **No licensing fees**: Pay only for the infrastructure you run it on
- **Transparent operations**: Every component is inspectable and auditable
- **Community-driven**: Improvements from other organizations benefit everyone
- **Future-proof**: If we disappeared tomorrow, you'd still have a working platform
## The SDK advantage
While the platform solves infrastructure, the SDK solves development complexity. Building with our SDK means your agents
automatically:
- Stream real-time updates to users through WebSocket connections
- Appear in the chat interface without custom UI development
- Get traced without instrumentation code
- Handle authentication and authorization without security logic
- Store state in provided databases without connection management
- Process documents through existing pipelines without custom parsing
You write business logic. The platform handles everything else.
## A practical example
Here's what deploying your first AI capability looks like:
1. **Clone the repository** and configure environment variables
2. **Run `docker compose up`** to start the platform
3. **Access the web UI** and authenticate with your SSO
4. **Chat with pre-built agents** that work immediately
5. **Connect your data sources** through the admin interface
6. **Build custom agents** using SDK patterns when needed
No infrastructure setup. No service provisioning. No complex configurations. The platform is ready for production from
day one.
This is infrastructure as a product: complete, functional, and yours to build upon.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/1_introduction/3_the_swiss_way.md
title: "The Swiss Way"
description: ""
---
# The Swiss way: Privacy, sovereignty, and transparency
The Swiss AI Hub embodies Swiss values, but not as marketing buzzwords. These principles exist for one reason: to build
trust. Swiss organizations don't adopt technology lightly, especially when it involves their data and tools. This
caution is justified - AI demonstrations are impressive, but production AI needs to earn trust through transparency,
control, and predictability.
## Trust through bounded behavior
Most AI platforms use open-ended agents: give them tools and a goal, let them figure out the rest. This approach works
in demos but creates anxiety in production. How do you know the agent won't do something unexpected? How do you audit
its decisions? How do you explain its failures?
The Swiss AI Hub takes a different approach:
**Closed-form workflows instead of open loops**\
Our agents follow explicit, step-by-step workflows. Each step defines what can happen, what data flows where, and what
decisions are possible. An agent can't suddenly decide to access data it shouldn't or perform actions you didn't
anticipate - it can only execute the workflow steps you've defined.
**Observable at every level**\
Trust requires visibility. The platform provides four layers of observability:
- **Infrastructure monitoring** through OpenTelemetry and Signoz tracks resource usage and API performance
- **Agent execution tracing** through OpenInference and Langfuse shows every LLM call and decision
- **Workflow event streams** make every step in the agent's process visible and debuggable
- **Pipeline observability** through Dagster shows exactly how your data is processed and where it goes
**Measurable performance**\
Hope is not a strategy. The platform includes evaluation frameworks that measure agent accuracy against test datasets.
You don't have to trust that agents work correctly - you can prove it with metrics.
## Trust through data sovereignty
::: warning Your data, your infrastructure
Data sovereignty isn't just about compliance - it's about control. When you deploy the Swiss AI Hub, your data never
leaves your infrastructure unless you explicitly configure it to. Run everything on-premise with local LLMs, and your
sensitive data never crosses your network boundary.
:::
This isn't theoretical capability. The platform ships with configurations for:
- **Fully on-premise deployment** with open-source models like Mistral or DeepSeek
- **Swiss cloud deployment** using Swiss data centers exclusively
- **Hybrid deployment** keeping sensitive data local while using cloud for non-critical workloads
You choose where each component runs, where data is stored, and which models process what information. This granular
control means you can start with maximum security and gradually relax constraints as trust builds.
## Trust through transparency
The platform's transparency goes beyond open source:
**Auditable decisions**\
Every agent decision is logged with full context. Not just what was decided, but why - what data was considered, what
rules were applied, what confidence levels were calculated. Compliance teams can trace any output back to its sources.
**Explainable workflows**\
Because agents follow defined workflows, you can explain their behavior to non-technical stakeholders. "The agent
analyzes the document, extracts key information, checks it against our rules, and then requests human approval" - not
"the AI does some processing."
**Visible integrations**\
When the platform connects to external systems, those connections are explicit and monitored. You see what data flows to
which services, what responses come back, and how they're processed.
## Trust through gradual adoption
Swiss organizations don't do "big bang" transformations, and the platform respects this:
**Start with read-only access**\
Begin with agents that only retrieve and analyze information. No write permissions, no system modifications, no
automated decisions. Build confidence through safe operations.
**Expand with human oversight**\
Add capabilities gradually, always with human approval steps. The agent prepares the work; humans verify and execute. As
trust grows, reduce oversight where appropriate.
**Isolate by criticality**\
Run separate instances for different security levels. Test new capabilities in development, validate in staging, deploy
to production only when proven. Critical systems can stay isolated while less sensitive ones integrate more deeply.
## Trust through professional engineering
The platform isn't a research project or a startup's MVP. It's built with Swiss engineering standards:
- **Comprehensive testing** at unit, integration, and system levels
- **Type safety** enforced throughout the codebase
- **Error handling** that gracefully degrades rather than fails catastrophically
- **Security by design** with authentication, authorization, and encryption built in
- **Professional documentation** that explains not just how but why
## The trust equation
Trust in AI comes from a simple equation:
**Predictability + Visibility + Control = Trust**
- **Predictability** through bounded workflows and defined behaviors
- **Visibility** through comprehensive observability and audit trails
- **Control** through data sovereignty and configuration options
The Swiss AI Hub provides all three. Not through promises or proprietary black boxes, but through open, inspectable,
modifiable infrastructure that you own and operate.
This is the Swiss way: earning trust through transparency, maintaining trust through reliability, and deserving trust
through respect for your data, your processes, and your caution. Because in Switzerland, trust isn't given - it's earned
through demonstration, maintained through consistency, and valued above rapid adoption.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/1_introduction/4_platform_vs_sdk.md
title: "Platform vs SDK"
description: ""
---
# Platform vs SDK: Understanding the dual architecture
The Swiss AI Hub separates platform from SDK deliberately. This separation isn't architectural complexity; it's
strategic design that gives you maximum flexibility in how you adopt, deploy, and extend the system.
## The platform: Your AI infrastructure
The platform is everything that runs when you execute `docker compose up`. It's the databases, message queues, LLM
gateway, vector stores, authentication system, user interfaces, and monitoring tools. Open-source (Apache 2.0 for the
runtime, AGPL-3.0-or-later for the UI and backup; see
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the per-package breakdown), it's yours
to deploy, modify, and operate however you need.
Think of the platform as your AI operating system. It handles:
- Where data is stored and how it's accessed
- Which models are available and how they're called
- Who can access what and how they're authenticated
- How components communicate and coordinate
- What users see and how they interact
The platform works immediately. Deploy it and you have a functional AI system with pre-built agents, chat interfaces,
and integrations. You can use it as-is without writing any code.
## The SDK: Your development framework
The SDK is how you build new capabilities that run on the platform. It provides base classes, decorators, patterns, and
tools that make your custom agents, pipelines, and processes automatically compatible with the platform infrastructure.
When you build with the SDK, you're writing business logic while the SDK handles platform integration:
```python
class MyAgent(Agent):
@step()
async def analyze_document(self, event: DocumentEvent) -> AnalysisEvent:
# Your business logic here
return AnalysisEvent(results=analysis)
```
This agent automatically:
- Streams updates through the platform's WebSocket connections
- Appears in the chat interface
- Gets traced in Langfuse
- Respects platform authentication
- Stores state in platform databases
- Handles errors according to platform patterns
## Why the separation matters
**Deployment flexibility**\
You can deploy the platform without any custom development code. Teams can start using AI immediately with pre-built
capabilities while developers work on custom agents. The platform provides value from day one.
**Development independence**\
SDK development happens outside the platform runtime. Developers work in their IDE with familiar tools, testing locally
before deploying. No need to understand the platform's internal architecture to build agents.
**Update isolation**\
Platform updates (new versions of Langfuse, LiteLLM, or the web UI) don't break your custom agents. SDK updates (new
decorators or patterns) don't require platform changes. Each layer evolves independently.
**Clear architectural boundaries**\
The separation creates clear interfaces between infrastructure and business logic. Platform concerns (authentication,
storage, tracing) are completely separated from agent logic. This makes the codebase easier to understand, test, and
maintain.
## How they work together
The magic happens through well-defined interfaces:
**Event contracts**\
The SDK defines event types that the platform understands. When your agent emits a `UserMessageEvent`, the platform
knows how to route it, store it, and display it. The contract is the event schema, not the implementation.
**Discovery protocols**\
Agents built with the SDK announce themselves to the platform through NATS messages. The platform discovers available
agents dynamically, no configuration required.
**Resource injection**\
The SDK knows about platform resources (databases, LLM clients, storage). When your agent needs them, the platform
injects configured instances automatically.
**Standardized patterns**\
The SDK enforces patterns the platform expects. Workflow steps, context management, error handling all follow
conventions the platform can monitor and manage.
## A practical comparison
Consider building a document analysis agent:
**Without the SDK:**
- Write custom API endpoints
- Implement WebSocket streaming
- Add authentication checks
- Configure database connections
- Instrument tracing manually
- Build a user interface
- Handle error states
- Manage deployment
**With the SDK:**
```python
class DocumentAnalyzer(Agent):
@step()
async def analyze(self, doc: Document) -> Analysis:
return analyze_document(doc)
```
The SDK isn't hiding complexity, it's eliminating redundancy. The platform already knows how to stream, authenticate,
store, trace, display, and deploy. The SDK provides the patterns to tap into these capabilities.
## Open source and licensing
Swiss AI Hub uses a mixed open-source model: the platform runtime, SDK, agents, pipelines and processes are licensed
under **Apache 2.0** (permissive — use, modify, distribute commercially, no obligation to share modifications back); the
web UI, the multi-tenant management plane, and backup orchestration under **AGPL-3.0-or-later** (network-copyleft). See
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the full per-package breakdown. The
components you typically extend — your own agents, pipelines and processes built on the SDK — sit under Apache 2.0, so
your code stays yours.
## When you need each part
**Platform only:** You want secure AI access with pre-built capabilities. Perfect for organizations starting their AI
journey or teams that need standard agents.
**Platform + SDK:** You need custom agents, specialized pipelines, or complex process automation. The SDK becomes
essential when pre-built capabilities aren't enough.
**SDK for migration:** You have existing agents built with other frameworks. The SDK provides migration paths to make
them platform-native while preserving your investment in business logic.
The platform runs your AI infrastructure. The SDK builds your competitive advantage. Together, they provide a complete
ecosystem where infrastructure is solved and development is streamlined, letting you focus on what makes your AI
applications unique.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/2_why_swiss_ai_hub.md
title: "Why Swiss AI Hub"
description: ""
---
# Why Swiss AI Hub?
Every AI solution makes trade-offs. Some prioritize ease of use over control. Others offer power but require massive
engineering investment. Understanding these trade-offs helps you choose the right approach for your organization.
This chapter provides practical comparisons and concrete advantages. You'll learn:
- How the Swiss AI Hub compares to popular alternatives across twelve critical enterprise needs
- Which production challenges are already solved in the platform
- How the collaborative ecosystem benefits every participant
Read this section when you need to justify your platform choice, understand what differentiates the Swiss AI Hub, or
explain the ecosystem model to stakeholders.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/2_why_swiss_ai_hub/1_comparison_matrix_light.md
title: "Comparison Matrix"
description: ""
---
# Comparison matrix: Where Swiss AI Hub fits
Different organizations need different AI solutions. Some prioritize ease of use, others need complete control.
Understanding these trade-offs helps you choose the right approach for your requirements.
## Market positioning (TL;DR)
This chapter explains when Swiss AI Hub is the right solution and when it isn't. But if you want the oversimplified
version:
```mermaid
quadrantChart
x-axis Low Data Sovereignty --> Full Data Sovereignty
y-axis Incomplete Platform --> Complete Platform
Swiss AI Hub: [0.9, 0.9]
LangChain: [0.90, 0.10]
Azure AI Foundry: [0.35, 0.80]
OpenAI Assistants: [0.10, 0.65]
Dify: [0.80, 0.45]
n8n: [0.75, 0.40]
```
Big cloud platforms give you everything out-of-the-box - authentication, monitoring, interfaces, the works. But you own
nothing and pay forever.
Programming frameworks like LangChain let you deploy anywhere and own the code. But they're just libraries. You handle
authentication, deployment, monitoring, and interfaces yourself.
Swiss AI Hub sits in the "Own Everything" quadrant: a complete, batteries-included platform that you deploy and own. You
get the completeness of cloud platforms with the ownership of open-source frameworks.
The rest of this chapter details the specific trade-offs. Read on for the nuanced picture, but if you're short on time:
**we give you a complete platform without vendor lock-in**.
## The 12 enterprise AI needs
We've identified twelve critical needs organizations face when adopting AI:
| Need | What it means | Why it matters |
| -------------------------- | ---------------------------------------------- | ------------------------------------------- |
| **Data sovereignty** | Control where data is stored and processed | Legal compliance and policy requirements |
| **Predictable costs** | Transparent pricing without surprises | Budget planning and ROI calculation |
| **Trust in outputs** | Visibility into AI reasoning and decisions | Risk management and user adoption |
| **Time to value** | Speed from deployment to working system | Demonstrating ROI and maintaining momentum |
| **Tool integration** | Compatibility with existing infrastructure | Avoiding workflow disruption |
| **Skill accessibility** | Enabling teams without AI expertise | Democratizing AI development |
| **Scalability** | Growing usage without complexity | Supporting organization-wide adoption |
| **Vendor independence** | Avoiding lock-in and maintaining control | Long-term flexibility and negotiating power |
| **Unified governance** | Consistent security and compliance | Meeting enterprise requirements |
| **Production reliability** | Consistent performance for critical operations | Business continuity |
| **Visual development** | Drag-and-drop workflow creation | Enabling citizen developers |
| **Zero maintenance** | Fully managed operations | Focusing on use cases, not infrastructure |
## How different approaches compare
### Swiss AI Hub position
The Swiss AI Hub provides:
- **Full marks** for sovereignty, cost control, trust, independence, and governance through self-hosted, open-source
architecture
- **Strong capabilities** in integration, skill bridging, scaling, and reliability through platform completeness
- **Quick deployment** with pre-built components while requiring some initial setup
- **Code-first approach** rather than visual development tools
### Libraries and frameworks
Tools like **LangChain**, **LlamaIndex**, and **Semantic Kernel** excel at providing AI development abstractions but
leave infrastructure entirely to you. They offer vendor independence through open source but require building everything
else: deployment, monitoring, authentication, user interfaces, and governance. These tools solve the AI logic problem
but create an infrastructure problem.
### Managed cloud platforms
Services like **Azure AI Foundry**, **Google Vertex AI**, and **AWS Bedrock** handle infrastructure complexity and
provide enterprise features. They trade sovereignty and independence for operational simplicity. Your data lives in
their cloud (even if region-selectable), you pay their margins indefinitely, and you work within their constraints. They
solve the infrastructure problem but create vendor lock-in.
### Visual development platforms
Platforms like **Dify** and **Flowise** democratize AI through drag-and-drop interfaces. They make AI accessible to
non-developers but often lack enterprise requirements like governance, detailed observability, and production
reliability. These platforms excel at rapid prototyping but struggle with complex, production-grade workflows that need
code-level control.
### Automation platforms with AI
Tools like **n8n** and **Zapier AI** are workflow automation platforms that added AI capabilities. They excel at
connecting systems and enabling non-technical users but treat AI as black-box components. They lack deep AI
observability, unified model management, and the transparency needed for trusted AI deployment.
## The detailed comparison
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :-------------------- | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| **LangChain** | ⚠️ | ❌ | ⚠️ | ❌ | ✅ | ⚠️ | ❌ | ✅ | ❌ | ❌ | ⚠️ | ❌ |
| **Azure AI Foundry** | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| **OpenAI Assistants** | ❌ | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ⚠️ | ✅ |
| **Dify** | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ✅ | ✅ |
| **n8n** | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ⚠️ | ✅ | ⚠️ |
> **Legend**\
> ✅ Full capability\
> ⚠️ Partial capability\
> ❌ Not addressed
## Making the right choice
The comparison reveals clear patterns:
**Choose libraries** (LangChain, LlamaIndex) when you have strong engineering teams who can build and maintain
infrastructure. You get maximum flexibility but must solve every production challenge yourself.
**Choose managed platforms** (Azure, Google, AWS) when operational simplicity outweighs sovereignty concerns. You get
reliability and scale but accept vendor lock-in and ongoing costs.
**Choose visual platforms** (Dify, Flowise) when rapid prototyping and citizen development are priorities. You get
accessibility but may hit limitations in production scenarios.
**Choose automation platforms** (n8n, Zapier) when AI is an enhancement to existing workflows rather than the core
capability. You get broad integration but limited AI depth.
**Choose Swiss AI Hub** when you need the completeness of a managed platform with the control of self-hosted
infrastructure. You get enterprise features, full sovereignty, and vendor independence, but must handle deployment and
operations yourself.
The Swiss AI Hub occupies a unique position: a complete platform that you own and control. This approach requires more
initial setup than managed services but provides long-term benefits in sovereignty, cost control, and flexibility that
compound over time.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/2_why_swiss_ai_hub/2_the_day_2_advantage.md
title: "The 'Day 2' Advantage"
description: ""
---
# The "Day 2" advantage: Problems we've already solved
Day 1 is the demo. The prototype works, stakeholders are impressed, and everyone's excited about AI. Day 2 is when
reality hits. The prototype needs to become a production system, and suddenly you're facing dozens of problems the demo
never addressed.
The Swiss AI Hub was built by teams who've lived through Day 2 many times. We've embedded solutions to these problems
directly into the platform, so you don't have to solve them yourself.
## Authentication and access control
**Day 2 problem:** Your prototype uses a hardcoded API key. Now you need user authentication, role management, and audit
trails. Do you build a user system from scratch? Integrate with Active Directory? How do you handle service accounts for
automated processes?
**Already solved:** The platform includes enterprise authentication with SSO/OAuth support. Connect to your identity
provider once, and every component inherits proper authentication. Users, agents, and processes all authenticate through
the same system. Role-based access control determines who can use which models, access which data, and perform which
actions. Every interaction is logged with user attribution.
## Cost explosion and tracking
**Day 2 problem:** The demo cost $50 in API calls. Production usage by 100 employees costs $50,000 in the first month.
Finance wants cost attribution by department. Management wants spending limits. No one knows which prompts are driving
costs.
**Already solved:** LiteLLM provides unified cost tracking across all model providers. Set quotas per user, team, or
globally. Track spending in real-time dashboards. See exactly which agents, prompts, and users generate costs. Export
detailed reports for chargeback. Automatic cutoffs prevent budget overruns.
## Multi-model complexity
**Day 2 problem:** Your prototype uses GPT-4. Production needs different models for different tasks: cheap models for
classification, powerful models for analysis, specialized models for code. Managing multiple API keys, handling
different response formats, and dealing with rate limits becomes a nightmare.
**Already solved:** The LiteLLM gateway provides a single interface to all models. Configure providers once, then
reference models by simple names. Automatic fallback when primary models are unavailable. Consistent request/response
format regardless of provider. Rate limiting and retry logic handled automatically.
## Data ingestion pipeline
**Day 2 problem:** The demo worked with 10 hand-picked documents. Production has 10,000 documents in various formats,
updating daily. You need document parsing, chunking strategies, embedding generation, and vector storage. Plus handling
updates when documents change.
**Already solved:** Dagster pipelines automatically process documents from configured sources. MinerU handles PDFs,
Office files, and complex formats. Smart chunking preserves document structure. Embeddings generated with configurable
models. Milvus provides production-grade vector storage. Changed documents trigger automatic reprocessing.
## Observability and debugging
**Day 2 problem:** The AI gives a wrong answer. What happened? Which documents did it reference? What was the actual
prompt sent to the model? How do you debug a system where every run is different?
**Already solved:** Multiple layers of observability are built in. Langfuse tracing shows every LLM call with inputs and
outputs. Workflow events make each step visible. Dagster provides complete pipeline lineage. OpenTelemetry tracks system
metrics. When something goes wrong, you can trace the entire execution path.
## User interface and access
**Day 2 problem:** Your prototype is a Python script. Users need a web interface, managers want dashboards, and everyone
expects it to work in Teams. Do you build a React app? Hire frontend developers? Create separate interfaces for
different user types?
**Already solved:** The platform includes a production-ready chat interface with voice, images, and documents. Process
cockpit for workflow participation. Admin dashboard for system management. Teams and Slack bots for users who prefer
those channels. WebSocket streaming for real-time updates. Everything connected to the same backend.
## Deployment and scaling
**Day 2 problem:** The prototype runs on a developer's laptop. Production needs high availability, horizontal scaling,
and zero-downtime updates. How do you containerize everything? Handle service discovery? Manage configurations across
environments?
**Already solved:** Everything runs in containers with Docker Compose for simple deployment or Kubernetes for scale.
NATS messaging enables automatic service discovery. Scale by running multiple agent instances. Configuration through
environment variables. Health checks and automatic restarts maintain availability.
## Testing and quality assurance
**Day 2 problem:** How do you test AI systems that give different responses each time? How do you ensure changes don't
break existing functionality? How do you validate agent behavior before production deployment?
**Already solved:** The SDK provides `AgentTestRunner` for deterministic testing. BDD patterns with pytest-bdd for
behavior verification. Evaluation frameworks to measure accuracy against test datasets. Sandbox environments for safe
testing. Langfuse tracing for test debugging.
## Compliance and governance
**Day 2 problem:** Legal needs audit trails. Compliance requires data lineage. Security wants to know who accessed what.
Privacy regulations demand PII handling. How do you add governance to a system that wasn't designed for it?
**Already solved:** Comprehensive audit logging tracks all actions. Data lineage from source to response. Presidio
provides PII detection and anonymization. Configurable data retention policies. Export capabilities for compliance
reporting. Everything designed with governance in mind from the start.
## Integration with existing systems
**Day 2 problem:** The prototype is standalone. Production needs to integrate with SharePoint, SAP, Salesforce, and
custom databases. Each integration requires different authentication methods, data formats, and error handling.
**Already solved:** OpenAI-compatible API for tool compatibility. Webhook endpoints for external system triggers. NATS
events for custom integrations. SharePoint connector included. Extensible resource system for adding new integrations.
Standard patterns for error handling and retry logic.
## Version management and updates
**Day 2 problem:** The prototype has no version control. Production needs to track which version of which agent produced
which output. Updates must be tested before deployment. Rollback capability is essential.
**Already solved:** Git-based version control for all components. Tagged container images for each version.
Configuration as code for reproducible deployments.
## The compound advantage
Each Day 2 problem solved saves weeks or months of development time. Together, they represent years of engineering
effort already complete. This isn't about features you might need someday. These are problems you will definitely face
when moving from prototype to production.
The Swiss AI Hub exists because we've been through Day 2 enough times to know what's coming. Instead of discovering
these problems one by one and scrambling for solutions, you start with a platform where they're already handled. Your
team can focus on building AI capabilities that matter to your business, not rebuilding infrastructure that should
already exist.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/2_why_swiss_ai_hub/3_the_ecosystem_model.md
title: "The Ecosystem Model"
description: ""
---
# The ecosystem model: How everyone benefits
The Swiss AI Hub exists because AI infrastructure shouldn't be a competitive differentiator. Basic capabilities like LLM
access, document processing, and RAG should be commodities, available to everyone. Swiss organizations should compete on
their domain expertise and business innovation, not on who can build better authentication systems or vector databases.
## The Swiss opportunity
Switzerland is a small country competing in a global economy dominated by tech giants. While Google, Microsoft, and
Amazon pour billions into AI infrastructure, Swiss companies individually lack the resources to match that investment.
The typical response would be to accept dependency on foreign platforms, but Switzerland has another option:
collaboration.
The same democratic principles that make Switzerland successful as a nation can transform how we approach AI. Instead of
each organization building redundant infrastructure, we can pool our efforts on the foundation while competing on the
application layer. This isn't about eliminating competition; it's about moving competition to where it creates value.
## Infrastructure as commodity
Consider what every organization building AI needs:
- Secure model access with cost controls
- Document processing and vector storage
- Authentication and user management
- Monitoring and observability
- Deployment and scaling patterns
These requirements are universal. A bank's authentication needs aren't fundamentally different from an insurance
company's. A pharmaceutical company's document processing challenges mirror those of a manufacturing firm. Yet today,
each organization either builds these capabilities separately or surrenders to a foreign platform.
The Swiss AI Hub makes this infrastructure a commodity. Deploy the platform once, and these problems are solved. Every
improvement to the core platform benefits every organization using it. When someone contributes better document parsing,
everyone's document processing improves. When someone adds a new security feature, everyone becomes more secure.
## The contribution dynamic
The Swiss AI Hub platform runtime, SDK, agents, pipelines, and processes are licensed under Apache 2.0. (The web UI, the
multi-tenant management plane, and backup orchestration are AGPL-3.0-or-later. See
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the per-package breakdown.) The
permissive license on the runtime + SDK creates natural collaboration incentives without forcing them.
**Shared infrastructure benefits everyone:** When organizations improve core infrastructure, sharing makes sense because
everyone benefits. A bank that adds better compliance logging helps every regulated industry. A healthcare provider that
improves PII handling helps everyone with privacy concerns. These contributions flow back naturally because better
shared infrastructure reduces everyone's costs.
**Strategic differentiation stays private:** Organizations keep their competitive advantages proprietary. The
customer-facing agent that embodies your unique business processes, your specialized data processing, your domain
expertise - these stay yours. The Apache 2.0 runtime doesn't require sharing anything back, so you're free to keep
strategic innovations private while benefiting from and contributing to shared infrastructure.
## Why licensing matters for AI infrastructure
Understanding software licensing is critical when building AI infrastructure. Many organizations make costly mistakes by
assuming that code on GitHub is automatically free to use - it's not.
### The GitHub misconception
**Seeing source code ≠ open source ≠ free to use.** GitHub hosts both open-source projects and proprietary software with
restrictive licenses. Being able to *view* or *download* code doesn't mean you're legally permitted to *use* it,
especially in production or commercial contexts.
Example: **n8n**, one of the most popular workflow automation tools on GitHub, uses the "Sustainable Use License" (not
an open-source license). While you can download and run it, the license prohibits commercial use without purchasing an
enterprise license - even if you self-host. Many organizations discover this too late, after building dependencies on
these tools.
### License categories explained
**Permissive licenses** (MIT, Apache 2.0, BSD): Grant broad freedoms - use, modify, distribute commercially, keep
modifications private. No strings attached. These are ideal for building business infrastructure.
**Copyleft licenses** (GPL, AGPL): Require that any modifications or derivative works be released under the same
license. If you build on GPL software and distribute it, you must open-source your entire application. **AGPL extends
this to network use** - even offering the software as a service triggers the requirement. This makes copyleft a poor fit
for the building blocks you extend with proprietary logic - which is exactly why the Swiss AI Hub runtime and SDK are
permissive, not copyleft. (Copyleft is the right choice for end-user applications such as the UI; see below.)
**Source-available licenses** (Elastic License, BSL, SSPL, "Sustainable Use"): Let you view and sometimes use the code,
but impose severe restrictions - often prohibiting commercial use, managed services, or competing products. Not
open-source despite appearing on GitHub.
**Proprietary/Custom licenses**: Vary widely. Require careful legal review. Often prohibit production use without
payment.
### Licenses to avoid in AI infrastructure
For production AI systems, be extremely cautious with:
- **AGPL/GPL**: Force your entire system open-source if you modify and distribute the software
- **SSPL (Server Side Public License)**: MongoDB's attempt to prevent cloud providers from offering managed versions;
triggers open-source requirements for infrastructure
- **Elastic License v2**: Prohibits offering the software as a competing service
- **Business Source License (BSL)**: Time-delayed open source; restrictive until expiration date
- **Custom "source-available" licenses**: Usually prohibit commercial use or have unclear terms
These licenses might seem acceptable initially, but create legal landmines when you scale, offer services, or integrate
with customer systems - **when they sit underneath the code you build on**. That is precisely why the Swiss AI Hub keeps
the runtime and SDK permissive. The web UI and backup orchestration are a deliberate exception: they are
AGPL-3.0-or-later *because* they are end-user applications rather than building blocks, so copyleft protects community
improvements without ever forcing your agents or business logic open.
### Our licensing commitment
The Swiss AI Hub rigorously evaluates every dependency - all 232 Python packages, 197 Node.js packages, and 28 external
Docker images. We verify that every component uses permissive licenses (MIT, Apache 2.0, BSD) or has been explicitly
reviewed and approved.
**You get the freedom each package's license confers — see
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the exact terms per package.** Briefly:
the runtime + SDK (Apache 2.0) place no restrictions on commercial use or integration with proprietary systems; the web
UI, the multi-tenant administration plane, and backup orchestration (AGPL-3.0-or-later) require source disclosure of
your modifications when you offer them as a network service.
**Why Apache 2.0 specifically for the runtime and SDK:** Beyond being permissive, Apache 2.0 includes explicit patent
grants, protecting you from patent claims by contributors. It's trusted by enterprises, well-understood by legal teams,
and compatible with virtually all other licenses. It's the gold standard for collaborative infrastructure — which is
precisely the role of the runtime and SDK.
This isn't just idealism - it's pragmatism. A permissive runtime + SDK removes barriers to adoption and prevents vendor
lock-in for the building blocks you extend; the AGPL components protect against hostile SaaS rehosts of the UI and the
administration plane without burdening the building blocks.
## Real collaboration patterns
The ecosystem already shows how this works:
**Shared components:** Common agents for document summarization, question answering, and data extraction become
community assets. Every organization needs these basics, so sharing makes sense.
**Industry solutions:** Healthcare organizations collaborate on medical document processing. Financial services share
compliance-focused agents. These industry-specific solutions emerge naturally when organizations realize their
competitors abroad, not domestically, are the real threat.
**Infrastructure improvements:** Performance optimizations, security enhancements, and operational tools flow back to
the platform. Everyone benefits from a faster, more secure, more reliable foundation.
**Knowledge sharing:** Organizations share deployment patterns, best practices, and lessons learned. The same platform
means solutions are transferable.
## Where competition belongs
The ecosystem model doesn't eliminate competition; it focuses it where it matters:
**Your domain expertise** remains yours. The platform doesn't know your business rules, your customer relationships, or
your market insights. These create competitive advantage.
**Your specialized agents** reflect your unique processes and knowledge. While you might share generic document
processing, your customer service agent embodies your specific approach.
**Your data and training** remain proprietary. The platform provides tools to process and query your data, but the data
itself and the insights derived from it are your competitive moat.
**Your business innovation** is where competition should be. Instead of competing on who has better vector databases,
compete on who uses AI more creatively to serve customers.
## The network effect
As more organizations adopt the Swiss AI Hub, the ecosystem strengthens:
**Development accelerates** because common patterns emerge and get encoded into the SDK. What took weeks to build
becomes a configuration option.
**Quality improves** through collective debugging and testing. With many organizations running the same platform, edge
cases get discovered and fixed quickly.
**Costs decrease** through shared investment. Instead of each organization paying for complete development, costs are
distributed across the ecosystem.
**Innovation increases** because developers can build on a higher foundation. Instead of reimplementing basics, they can
explore new capabilities.
## The Swiss AI advantage
This collaborative approach gives Swiss organizations collective advantages:
**Speed:** New organizations can deploy production AI in days, not months, by leveraging existing work.
**Sovereignty:** Swiss data stays in Switzerland, processed by Swiss-controlled infrastructure, governed by Swiss law.
**Independence:** No single vendor controls the platform. No foreign company can change terms or cut access.
**Quality:** Collective investment produces better infrastructure than any single organization could build.
**Innovation:** Freed from infrastructure concerns, organizations focus on business innovation where real value lies.
## Making collaboration work
The ecosystem succeeds because it aligns incentives correctly:
Organizations contribute to infrastructure because they benefit directly from improvements. They share
non-differentiating capabilities because collaboration is more valuable than secrecy. They keep strategic innovations
private because Apache 2.0 permits both approaches - the choice is always yours.
The Swiss AI Hub provides the technical foundation for this collaboration, but the ecosystem is built by its members.
Every organization that deploys the platform, contributes improvements, or shares knowledge strengthens Swiss AI
capabilities collectively.
This is how Switzerland competes globally: not through individual organizations trying to match Big Tech resources, but
through collaborative infrastructure that lets every organization focus on what makes them unique. The platform is the
commodity layer that makes innovation possible.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/2_why_swiss_ai_hub/4_comparison_matrix_full.md
title: "Full Competitor Analysis"
description: ""
---
# Full Competitor Analysis
This comprehensive analysis compares the Swiss AI Hub against its competitors in the market, which are categorized in to
platforms, frameworks, and solutions.
## Libraries and Frameworks
These are developer-focused tools and frameworks that provide building blocks for AI applications. They offer
flexibility and control but require significant development effort to build complete, production-ready systems.
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :--------------- | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| LangChain | ⚠️ | ❌ | ⚠️ | ❌ | ✅ | ⚠️ | ❌ | ✅ | ❌ | ❌ | ⚠️ | ❌ |
| LangGraph | ⚠️ | ⚠️ | ✅ | ❌ | ⚠️ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| LlamaIndex | ⚠️ | ❌ | ⚠️ | ⚠️ | ✅ | ⚠️ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Semantic Kernel | ⚠️ | ⚠️ | ⚠️ | ❌ | ⚠️ | ✅ | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ |
| AutoGen | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| CrewAI | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Haystack | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| DSPy | ⚠️ | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
### Library Details
::: details LangChain
LangChain is a powerful library for building LLM applications, but it's not a platform. While it excels at providing
abstractions and integrations for AI development, it leaves deployment, monitoring, authentication, cost control, and
user interfaces entirely to you. You can achieve sovereignty by deploying your code anywhere, but you must build all the
infrastructure yourself. LangSmith adds observability but requires separate setup and subscription.
**Choose LangChain when** you have strong engineering teams who want maximum flexibility and are willing to build all
infrastructure components from scratch. You need custom AI logic that doesn't fit standard patterns, or you're building
a specialized AI product where the framework is just one component.
**Choose Swiss AI Hub when** you want the power of frameworks like LangChain but with a complete platform that handles
deployment, authentication, monitoring, user interfaces, and governance out-of-the-box. You get the same development
flexibility but without all the infrastructure work.
:::
::: details LangGraph
LangGraph excels at building stateful, observable agent workflows with sophisticated control flow. As a Python library,
it provides excellent abstractions for agent development but requires you to build all infrastructure, deployment,
monitoring, authentication, and user interfaces yourself. You get the agent logic, not the platform to run it on.
**Choose LangGraph when** you need sophisticated multi-agent workflows with complex state management and have the
resources to build a complete platform around it. Your use case requires custom agent architectures that don't fit
standard patterns.
**Choose Swiss AI Hub when** you want advanced agent capabilities but also need enterprise features like authentication,
monitoring, cost control, and user interfaces immediately. You get sophisticated workflows plus a production-ready
platform without the development overhead.
:::
::: details LlamaIndex
LlamaIndex excels at RAG and data ingestion with sophisticated document processing and retrieval patterns. As a Python
library, it provides powerful abstractions but no infrastructure - you still need to handle deployment, authentication,
monitoring, and user interfaces yourself. While you can achieve sovereignty and observability by building around it,
these aren't built-in capabilities.
**Choose LlamaIndex when** you're building a specialized RAG system with unique data processing requirements and have
the engineering capacity to build all supporting infrastructure. Your document processing needs are highly customized.
**Choose Swiss AI Hub when** you want powerful RAG capabilities (built on LlamaIndex) but with enterprise-ready
deployment, authentication, data governance, and user interfaces included. You get the same RAG power with complete
platform features from day one.
:::
::: details Semantic Kernel
Semantic Kernel is Microsoft's well-designed orchestration framework that provides excellent abstractions for AI
development. As a library, it offers powerful planning and plugin capabilities and integrates well with Azure services.
**Choose Semantic Kernel when** you're deeply invested in the Microsoft ecosystem, need sophisticated AI planning
capabilities, and have the resources to build production infrastructure. You want Microsoft's AI abstractions with
custom platform development.
**Choose Swiss AI Hub when** you want enterprise AI capabilities without being locked into Microsoft's ecosystem or
building infrastructure yourself. You get similar orchestration power with complete data sovereignty, transparent costs,
and a ready-to-deploy platform.
:::
::: details AutoGen
AutoGen excels at multi-agent conversation patterns and provides excellent abstractions for complex agent interactions.
As a Python library, it leaves deployment, monitoring, authentication, and production operations entirely to the
developer. While you can achieve data sovereignty and integration by building around it, these capabilities aren't
inherent to the framework.
**Choose AutoGen when** you need specialized multi-agent conversation patterns and have the engineering team to build a
complete production environment. Your use case centers on agent-to-agent communication with custom interaction patterns.
**Choose Swiss AI Hub when** you want multi-agent capabilities within a complete enterprise platform that handles
deployment, governance, authentication, and monitoring automatically. You get agent collaboration plus the
infrastructure to run it reliably in production.
:::
::: details CrewAI
CrewAI is a multi-agent orchestration library that simplifies building collaborative AI teams and excels at defining
agent roles and workflows. It is open source and runs wherever you deploy it.
**Choose CrewAI when** you want to experiment with multi-agent scenarios and have strong development capabilities to
build supporting infrastructure. Your focus is on agent collaboration patterns rather than production deployment.
**Choose Swiss AI Hub when** you want multi-agent orchestration within a complete, production-ready platform that
includes deployment, authentication, monitoring, and governance. You get agent collaboration plus enterprise features
without building infrastructure from scratch.
:::
::: details Haystack
Haystack is an excellent open-source framework for building RAG pipelines and search systems. It provides powerful
abstractions for document processing and retrieval, which are the building blocks for AI applications.
**Choose Haystack when** you need specialized search and RAG capabilities with deep customization, and you have the
resources to build all supporting infrastructure. Your search requirements are highly specialized or research-focused.
**Choose Swiss AI Hub when** you want powerful search and RAG capabilities (including Haystack-compatible patterns)
within a complete platform that provides deployment, authentication, governance, and user interfaces immediately. You
get search power plus enterprise readiness.
:::
::: details DSPy
DSPy is a powerful framework for programmatically optimizing LLM applications through automatic prompt engineering. It
excels at systematic evaluation and prompt optimization, which makes it great for research and prototypes.
**Choose DSPy when** you're conducting AI research or need advanced prompt optimization techniques and have the
resources to build production infrastructure. Your primary focus is on experimental AI techniques rather than deployed
applications.
**Choose Swiss AI Hub when** you want a production-ready platform for building AI systems with comprehensive monitoring
and governance. You get enterprise infrastructure for deploying reliable AI applications, though optimization and
development require coding expertise rather than automated tools.
:::
## Swiss/European AI Providers
These are AI platforms and providers based in Switzerland or Europe, focusing on data sovereignty, regulatory
compliance, and regional data protection requirements. They prioritize keeping data within European jurisdictions while
offering various AI capabilities.
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :------------------ | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Alpine AI | ✅ | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
| Abacus Deep | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ❌ | ✅ | ✅ | ⚠️ | ⚠️ |
| BrandBot (Begasoft) | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ⚠️ | ❌ | ❌ |
| Envoya AI | ✅ | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ⚠️ | ❌ |
| Aleph Alpha | ✅ | ❌ | ✅ | ⚠️ | ⚠️ | ❌ | ⚠️ | ✅ | ⚠️ | ⚠️ | ❌ | ❌ |
| owwn.ai | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ⚠️ | ⚠️ | ❌ | ❌ | ❌ |
| PREM | ✅ | ❌ | ⚠️ | ❌ | ⚠️ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Private AI Suite | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ⚠️ | ⚠️ | ❌ |
### Swiss/European Provider Details
::: details Alpine AI
Alpine AI (SwissGPT) is a Swiss AI platform specifically targeting critical and regulated sectors with strong compliance
focus. While they excel at Swiss data sovereignty and regulatory compliance.
**Choose Alpine AI when** you're in a highly regulated sector that requires Swiss compliance.
**Choose Swiss AI Hub when** you want Swiss sovereignty with complete transparency about platform capabilities,
architecture, and costs. You get regulatory compliance with full visibility into how the platform works, enabling
informed technical and business decisions.
:::
::: details Abacus Deep
Abacus Deep is a comprehensive Swiss ERP platform with AI-powered modules for document management and autonomous
accounting. Hosted exclusively in Swiss data centers with ISO 27001:2022 certification, it excels at Swiss compliance
and security. However, as an integrated ERP solution, it creates significant vendor lock-in.
**Choose Abacus Deep when** you're a Swiss SME that needs a complete ERP system and wants AI features integrated into
your business processes. You're looking for an all-in-one business management solution rather than a dedicated AI
platform.
**Choose Swiss AI Hub when** you want to build custom AI applications that integrate with your existing ERP system
(including Abacus) without being locked into a single vendor's business software. You get AI platform flexibility while
maintaining Swiss compliance and data sovereignty.
:::
::: details BrandBot (Begasoft)
BrandBot is a 100% Swiss-hosted AI platform with ISO compliance and OpenAI-compatible APIs, targeting Swiss enterprises
and public administration. It provides strong Swiss regulatory compliance, audit logging, and role-based access
controls.
**Choose BrandBot when** you need a Swiss-hosted AI platform with OpenAI-compatible APIs and your requirements are
relatively straightforward. You value simplicity and Swiss hosting over advanced platform features.
**Choose Swiss AI Hub when** you want Swiss hosting plus a comprehensive, enterprise-grade platform with advanced
features like workflow orchestration, data pipelines, observability, and extensible architecture. You get Swiss
sovereignty with platform completeness and transparency.
:::
::: details Envoya AI
Envoya AI is a Swiss AI platform offering comprehensive enterprise tools and Swiss data center hosting. It provides
DSG/GDPR compliance, pre-configured AI agents, and flexible scaling. However, as a newer platform, it may lack
production reliability proof and creates some platform dependency. While excellent for Swiss enterprises seeking
cost-effective AI with sovereignty, it may need time to mature.
**Choose Envoya AI when** you want cost-effective Swiss AI with simple flat-rate pricing and your needs fit their
pre-configured agents.
**Choose Swiss AI Hub when** you need Swiss sovereignty, transparent costs, and complete control over your AI platform.
You get infrastructure with full customization capabilities and vendor independence through open-source architecture.
:::
::: details Aleph Alpha
Aleph Alpha is a European AI company providing the PhariaAI sovereign AI suite for governments and enterprises. They
emphasize "explainable AI" with their AtMan (Attention Manipulation) transparency technology and offer domain-specific
solutions. While they excel at European sovereignty and compliance, they lack transparent pricing and require
significant technical expertise. Their "no vendor lock-in" promise and German sovereign infrastructure make them
attractive for regulated industries, but they're more of an AI model provider than a complete platform.
**Choose Aleph Alpha when** you're a government or highly regulated enterprise that needs European AI models with
explainability features, and you have the technical expertise to integrate their models into your own infrastructure.
Compliance with German/EU regulations is your primary concern.
**Choose Swiss AI Hub when** you want European sovereignty with Swiss data protection, but also need a complete,
ready-to-deploy platform rather than just AI models. You get sovereignty plus enterprise features like authentication,
monitoring, and governance without requiring deep AI expertise.
:::
::: details owwn.ai
owwn.ai is a Swiss AI solution provider offering customizable AI systems with strong data sovereignty guarantees. They
keep data in Swiss data centers, support multiple LLM providers, and integrate with existing enterprise systems. While
they provide sovereignty without additional licensing costs, they're primarily a consulting-based service rather than a
self-service platform. They excel at Swiss compliance but may lack the scalability and platform completeness needed for
large enterprises.
**Choose owwn.ai when** you need heavily customized AI solutions with Swiss hosting and prefer a consulting-led
approach. Your requirements are highly specific and you value personalized service over self-service capabilities.
**Choose Swiss AI Hub when** you want Swiss sovereignty with a self-service, scalable platform that your team can deploy
and manage independently. You get the same Swiss compliance with greater control, transparency, and platform
completeness for enterprise-wide adoption.
:::
::: details PREM
PREM is an applied AI research platform focused on sovereign, private AI models with their TrustML™ encryption
framework. They offer autonomous fine-tuning and cost-efficient inference, supporting both cloud and local deployment.
While they excel at privacy-preserving AI and cost reduction, they require significant technical expertise and are more
research-oriented than production-ready. Their specialized reasoning models and open-source components provide vendor
independence but at the cost of complexity.
**Choose PREM when** you're conducting AI research, need cutting-edge privacy-preserving techniques, and have deep
technical expertise to handle complex, experimental systems. Your primary focus is on advanced AI research rather than
production deployment.
**Choose Swiss AI Hub when** you want privacy and sovereignty with a production-ready platform that doesn't require
specialized AI research expertise. You get data protection and vendor independence with enterprise features, user
interfaces, and operational simplicity.
:::
::: details Private AI Suite
Private AI Suite is a comprehensive Swiss AI platform with modular privacy-focused components and "Swiss-grade privacy"
guarantees. It provides Swiss regulatory compliance, modular architecture, and serves government and enterprise clients.
**Choose Private AI Suite when** you're a large enterprise or government organization with substantial budget and need
comprehensive privacy guarantees. You value their modular approach and can justify enterprise-level pricing and vendor
lock-in.
**Choose Swiss AI Hub when** you want Swiss privacy and sovereignty with predictable costs and complete vendor
independence. You get comprehensive AI capabilities with transparent pricing, open-source architecture, and the
flexibility to deploy at any scale without vendor lock-in.
:::
## Managed Cloud Platforms
These are comprehensive, fully-managed cloud services from major technology vendors that handle infrastructure, scaling,
and operations. They offer convenience and enterprise-grade reliability but typically require vendor lock-in and limit
data sovereignty options.
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :------------------ | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Azure AI Foundry | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Microsoft Copilot | ❌ | ⚠️ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ⚠️ | ✅ | ❌ | ✅ |
| Google Vertex AI | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ |
| AWS Bedrock | ⚠️ | ⚠️ | ❌ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| IBM watsonx | ⚠️ | ❌ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ⚠️ | ❌ |
| Oracle AI | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ⚠️ | ✅ | ❌ | ✅ |
| SAP Business AI | ⚠️ | ❌ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| Salesforce Einstein | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
### Cloud Platform Details
::: details Azure AI Foundry
Azure AI Foundry is Microsoft's comprehensive enterprise AI platform, offering managed infrastructure with excellent
Microsoft ecosystem integration. While it provides visual development tools and handles all operational complexity,
you're locked into Microsoft's ecosystem with their pricing model and limited visibility into AI decision-making. Data
can be kept in Swiss Azure regions, but remains under Microsoft's control and governance.
**Choose Azure AI Foundry when** you're heavily invested in the Microsoft ecosystem, need zero infrastructure
management, and are comfortable with vendor lock-in and Microsoft's pricing model. Your team prefers visual development
tools over code-based approaches.
**Choose Swiss AI Hub when** you want enterprise AI capabilities without vendor lock-in, with complete control over your
data and infrastructure. You get similar enterprise features with full sovereignty, transparent costs, and the ability
to deploy anywhere, including on-premises.
:::
::: details Microsoft Copilot
Microsoft Copilot embeds AI directly into Office applications, providing immediate productivity gains without any
development. However, it's a closed product, not a platform. You can't build custom agents, control where data is
processed, or see how decisions are made. Perfect for office productivity, unsuitable for building your own AI
applications.
**Choose Microsoft Copilot when** you want immediate productivity gains in Office applications without any development
effort, and you're comfortable with Microsoft processing your data through their systems.
**Choose Swiss AI Hub when** you want to build custom AI applications that integrate with your business processes and
data, with full control over where processing happens. You get productivity gains plus the ability to create specialized
AI solutions for your organization.
:::
::: details Google Vertex AI
Google Vertex AI is a comprehensive, managed AI platform that handles infrastructure complexity for you. While it
provides enterprise-grade reliability and seamless scaling within Google Cloud, you trade control for convenience. Data
remains in Google's infrastructure (though region-selectable), costs can be unpredictable with complex pricing tiers,
and you're locked into their ecosystem.
**Choose Google Vertex AI when** you're fully committed to Google Cloud, have complex AI workloads that benefit from
Google's ML expertise, and operational simplicity is more important than data sovereignty or cost predictability.
**Choose Swiss AI Hub when** you want comprehensive AI capabilities with predictable costs, complete data sovereignty,
and the flexibility to deploy on any infrastructure. You get enterprise-grade features without vendor lock-in or
unpredictable pricing.
:::
::: details AWS Bedrock
AWS Bedrock is a managed model serving platform that provides access to foundation models through APIs. While it handles
model infrastructure excellently and integrates seamlessly with AWS services, it's not a complete AI application
platform. You still need to build all application logic, user interfaces, and data pipelines yourself. Data remains in
AWS infrastructure (though you can choose regions), and you're locked into AWS's ecosystem and pricing model.
**Choose AWS Bedrock when** you're fully committed to AWS, need access to multiple foundation models, and have the
resources to build complete applications around model APIs. You prioritize AWS integration over platform completeness.
**Choose Swiss AI Hub when** you want a complete AI platform with foundation model access, application logic, user
interfaces, and data pipelines included. You get comprehensive capabilities with data sovereignty and the flexibility to
deploy anywhere.
:::
::: details IBM watsonx
IBM watsonx is a comprehensive AI and data platform with a hybrid cloud approach and strong focus on AI ethics. It
supports deployment across multiple clouds and emphasizes responsible AI development. While it provides enterprise-grade
reliability and industry-specific solutions, it comes with typical IBM complexity and lacks transparent pricing. The
platform offers good integration capabilities but creates potential vendor lock-in through its comprehensive ecosystem.
**Choose IBM watsonx when** you're an enterprise customer comfortable with IBM's complexity and pricing model, need
industry-specific AI solutions, and value IBM's decades of enterprise experience over simplicity.
**Choose Swiss AI Hub when** you want comprehensive AI capabilities without vendor complexity, with transparent pricing
and complete control over your platform. You get enterprise features with simplicity, sovereignty, and clear cost
structure.
:::
::: details Oracle AI
Oracle AI provides comprehensive AI services through Oracle Cloud Infrastructure, including generative AI, language,
speech, and vision capabilities. It offers enterprise-grade security and customizable models, but is cloud-only with
strong potential for vendor lock-in. While it provides reliable infrastructure and 20+ years of data science experience,
it lacks data sovereignty options and requires commitment to Oracle's ecosystem.
**Choose Oracle AI when** you're an existing Oracle customer with significant investment in Oracle infrastructure and
want AI capabilities deeply integrated with your Oracle systems. You value Oracle's enterprise reliability over
sovereignty.
**Choose Swiss AI Hub when** you want enterprise AI capabilities without being locked into Oracle's ecosystem, with full
data sovereignty and deployment flexibility. You get comprehensive AI features with the freedom to integrate with any
system.
:::
::: details SAP Business AI
SAP Business AI features the Joule AI assistant with over 240 AI scenarios and integration across 13 SAP solutions. It
provides comprehensive enterprise AI capabilities with strong governance and multi-language support. However, it's
deeply integrated with SAP's ecosystem, creating vendor lock-in, and lacks transparent pricing. While excellent for SAP
customers, it requires significant investment in SAP infrastructure and may not be cost-effective for non-SAP
environments.
**Choose SAP Business AI when** you're heavily invested in SAP's ecosystem, need AI deeply integrated with SAP business
processes, and are comfortable with SAP's pricing and infrastructure requirements.
**Choose Swiss AI Hub when** you want to integrate AI with your business processes (including SAP systems) without being
locked into any single vendor's ecosystem. You get business AI capabilities with flexibility, sovereignty, and
transparent costs.
:::
::: details Salesforce Einstein
Salesforce Einstein provides AI natively embedded in the Salesforce CRM platform with the Einstein Trust Layer for data
protection. It offers comprehensive AI agents, workflow automation, and industry-specific solutions. While it excels at
CRM-integrated AI and provides ethical AI features, it's limited to the Salesforce ecosystem and lacks data sovereignty
options. Perfect for Salesforce customers but unsuitable for organizations seeking platform-independent AI solutions.
**Choose Salesforce Einstein when** you're a Salesforce customer who wants AI deeply integrated into CRM workflows
without additional platform complexity. Your AI needs are primarily CRM-focused.
**Choose Swiss AI Hub when** you want AI capabilities that extend beyond CRM to all business processes, with data
sovereignty and platform independence. You can integrate with Salesforce while building AI solutions for your entire
organization.
:::
## Visual Development Platforms
These are platforms that emphasize drag-and-drop, no-code/low-code approaches to building AI applications. They
prioritize accessibility for non-technical users but may sacrifice flexibility and enterprise-grade features for ease of
use.
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :--------------- | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Dify | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ✅ | ✅ |
| Flowise | ✅ | ⚠️ | ❌ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ |
| LangFlow | ⚠️ | ⚠️ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ |
### Visual Platform Details
::: details Dify
Dify is an open-source platform for building AI applications using visual, drag-and-drop workflows. It allows
non-technical team members to create AI applications by connecting nodes (like calling AI models, searching databases,
or executing logic) on a visual canvas. It excels at rapid prototyping and making AI development accessible to product
managers and domain experts.
**Choose Dify when** you want rapid prototyping with visual workflows, need non-technical team members to build AI
applications, and your use cases fit well within drag-and-drop paradigms. You prioritize development speed and
accessibility over deep customization.
**Choose Swiss AI Hub when** you need enterprise-grade governance and observability with code-based development for
complex AI systems. You get a complete platform for building auditable, customizable AI applications with transparent
monitoring, but development requires coding rather than visual tools.
:::
::: details Flowise
Flowise excels at making AI accessible through visual, drag-and-drop flow building. It's self-hostable and open source,
which provides sovereignty and independence. However, it's primarily a development tool rather than a production
platform. It lacks enterprise features like proper authentication, scaling mechanisms, governance controls, and
production-grade reliability. Best suited for rapid prototyping and development, not enterprise deployments.
**Choose Flowise when** you're prototyping AI workflows, want a simple visual interface, and don't need enterprise-grade
features. Your use case is experimental or educational rather than production-focused.
**Choose Swiss AI Hub when** you want a production-ready platform with authentication, governance, scaling, and
reliability, and you're comfortable with code-based development. You get enterprise readiness with complete platform
control, though you'll need coding skills rather than visual tools.
:::
::: details LangFlow
LangFlow is a visual interface for LangChain that accelerates prototype development through drag-and-drop workflow
creation. While it excels at making AI accessible to non-developers, it's a development tool, not a production platform.
It lacks built-in authentication, monitoring, cost tracking, and deployment infrastructure - you still need to figure
out how to run, scale, and secure your flows in production.
**Choose LangFlow when** you want to quickly prototype LangChain-based workflows with a visual interface and have the
resources to build production infrastructure around your prototypes. Your focus is on rapid experimentation.
**Choose Swiss AI Hub when** you want to build LangChain-compatible workflows within a complete production platform that
handles authentication, monitoring, deployment, and scaling automatically. You get production readiness with code-based
development rather than visual prototyping tools.
:::
## Automation Platforms with AI
These are workflow automation platforms that have integrated AI capabilities as additional features. They excel at
connecting systems and automating business processes, with AI serving as supporting functionality rather than their
primary focus.
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :--------------- | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| n8n | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ⚠️ | ✅ | ⚠️ |
| Zapier AI | ❌ | ⚠️ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ❌ | ⚠️ | ✅ | ✅ | ✅ |
| Make | ⚠️ | ⚠️ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ❌ | ⚠️ | ✅ | ✅ | ✅ |
### Automation Platform Details
::: details n8n
n8n is an excellent workflow automation platform that adds AI capabilities through nodes. While it excels at visual
workflow creation and has hundreds of integrations, it lacks the deep AI infrastructure of a dedicated platform. There's
no built-in observability for AI decisions, no unified LLM gateway, and limited enterprise governance features. It's
automation-first with AI added, not AI-native.
**Choose n8n when** you need comprehensive workflow automation with some AI capabilities, have many system integrations
to manage, and AI is a supporting feature rather than your core requirement. You value broad connectivity over AI depth.
**Choose Swiss AI Hub when** AI is central to your workflows and you need deep AI observability, unified model
management, and enterprise governance. You get workflow automation plus comprehensive AI infrastructure designed for
AI-first applications.
:::
::: details Zapier AI
Zapier AI extends a workflow automation platform with AI capabilities rather than providing AI infrastructure. While it
excels at connecting tools and enabling non-technical users to build automations, it operates as a black-box cloud
service without visibility into AI decision-making, data sovereignty options, or deployment flexibility.
**Choose Zapier AI when** you need simple AI-enhanced automations between SaaS tools, want zero maintenance, and are
comfortable with cloud-only deployment and black-box AI operations. Your needs are straightforward and compliance
requirements are minimal.
**Choose Swiss AI Hub when** you need transparent AI operations with full visibility into decision-making, data
sovereignty, and deployment control. You get powerful automation capabilities with complete transparency, governance,
and the ability to deploy anywhere.
:::
::: details Make (formerly Integromat)
Make is a visual automation platform that added AI capabilities as modules within workflows. While excellent for no-code
automation with thousands of integrations, it treats AI as black-box components without visibility into reasoning or
decisions. Being a proprietary SaaS platform, it offers convenience but lacks data sovereignty, vendor independence, and
the deep AI observability enterprises need for trust.
**Choose Make when** you need extensive no-code integrations with some AI features, prioritize convenience over control,
and are comfortable with proprietary SaaS limitations. Your AI needs are simple and transparency isn't critical.
**Choose Swiss AI Hub when** you need comprehensive AI capabilities with full observability, data sovereignty, and
vendor independence. You get powerful automation plus transparent AI operations that enterprises can trust and audit.
:::
## Business Process Platforms
These are enterprise-grade platforms designed for managing, automating, and optimizing complex business processes. They
focus on workflow orchestration, case management, and process mining, with AI capabilities integrated to enhance
traditional business process management.
| Framework | Data sovereignty | Predictable costs | Trust in outputs | Time to Value | Tool integration | Skill accessibility | Scalability | Vendor independence | Unified governance | Production reliability | Visual development | Zero maintenance |
| :------------------ | :--------------: | :---------------: | :--------------: | :-----------: | :--------------: | :-----------------: | :---------: | :-----------------: | :----------------: | :--------------------: | :----------------: | :--------------: |
| **Swiss AI Hub** | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Camunda | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Automation Anywhere | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| Pega | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| Appian | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| Blue Prism | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ❌ |
| Celonis | ⚠️ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ⚠️ | ✅ | ⚠️ | ❌ |
| Flowable | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
### Business Process Platform Details
::: details Camunda
Camunda is a process orchestration platform that has integrated AI agent capabilities while maintaining its BPMN-based
approach. It provides excellent process transparency, open standards compliance, and enterprise-proven scalability.
**Choose Camunda when** you have complex business processes that require BPMN modeling, need enterprise-grade process
orchestration, and have teams with BPMN expertise. Your primary focus is on process management with AI as a supporting
component.
**Choose Swiss AI Hub when** you want AI-first process automation through code-based development, with built-in AI
capabilities. You get powerful process orchestration designed specifically for AI workflows, though you'll need
programming skills rather than visual modeling tools.
:::
::: details Automation Anywhere
Automation Anywhere is an enterprise RPA platform leader with agentic process automation. It provides comprehensive
governance, enterprise app compatibility, and Process Reasoning Engine transparency. However, it requires RPA expertise,
creates platform lock-in, and needs significant IT management. While proven at enterprise scale, it may be overly
complex for organizations seeking simpler AI solutions.
**Choose Automation Anywhere when** you're a large enterprise with significant RPA investments, need to scale
traditional automation massively, and have teams with deep RPA expertise. Your automation strategy is RPA-first with AI
integration.
**Choose Swiss AI Hub when** you want AI-first automation without RPA complexity, with transparent architecture and
vendor independence. You get enterprise-scale capabilities designed for modern AI workflows without the overhead of
traditional RPA platforms.
:::
::: details Pega
Pega is a low-code platform specializing in "Predictable AI" with comprehensive agentic workflows and case management.
It provides enterprise-grade governance, scalability, and strong process transparency. However, it creates significant
platform lock-in, has complex enterprise pricing, and requires platform-specific expertise. While excellent for large
enterprises with complex case management needs, it may be overkill for organizations seeking simpler AI automation
solutions.
**Choose Pega when** you're a large enterprise with complex case management requirements, substantial budget for
platform licensing, and teams that can develop Pega-specific expertise. Your processes are highly complex and justify
platform investment.
**Choose Swiss AI Hub when** you want powerful AI and process capabilities without vendor lock-in, with transparent
pricing and platform independence. You get enterprise-grade features with the flexibility to adapt and extend without
proprietary constraints.
:::
::: details Appian
Appian is a low-code automation platform with private AI integration and comprehensive data fabric capabilities. It
provides enterprise governance, rapid development capabilities, and strong security features. While it offers good
scalability and process transparency, it creates platform dependency and requires ongoing platform management. The
platform excels at enterprise process automation but lacks vendor independence and may be costly for smaller
organizations.
**Choose Appian when** you need rapid low-code development for enterprise processes, have budget for platform licensing,
and are comfortable with platform dependency. Your focus is on quick application development rather than AI innovation.
**Choose Swiss AI Hub when** you want enterprise process automation with AI-first design, complete vendor independence,
and transparent costs. You get rapid development capabilities plus the flexibility to innovate and extend without
platform constraints.
:::
::: details Blue Prism
Blue Prism is a mature enterprise RPA platform that has evolved to include AI integration and intelligent automation. It
provides strong governance, enterprise-proven scalability, and comprehensive process automation capabilities. While it
excels at structured process automation, it requires specialized RPA expertise and significant IT management overhead.
The platform creates vendor lock-in through platform-specific automation and may be complex for organizations seeking
simpler AI solutions.
**Choose Blue Prism when** you have substantial RPA investments, need to automate highly structured processes, and have
teams with specialized RPA expertise. Your automation needs are primarily traditional RPA with some AI enhancement.
**Choose Swiss AI Hub when** you want intelligent automation without RPA complexity, with AI-native design through
code-based development. You get powerful automation capabilities designed for AI workflows without requiring specialized
RPA knowledge, though you'll need programming skills.
:::
::: details Celonis
Celonis is a process intelligence platform specializing in AI-powered process mining and optimization. It provides
data-driven insights with enterprise-proven scalability. However, it requires specialized process mining expertise,
creates platform dependency, and focuses primarily on process analysis rather than automation. While excellent for
process optimization, it's not a general-purpose AI platform and may require significant additional tooling for complete
AI solutions.
**Choose Celonis when** your primary need is process mining and optimization, you have specialized process intelligence
expertise, and you're focused on understanding existing processes rather than building new AI applications.
**Choose Swiss AI Hub when** you want comprehensive AI capabilities that include process optimization plus the ability
to build and deploy AI applications. You get process intelligence as part of a complete AI platform rather than as a
specialized standalone tool.
:::
::: details Flowable
Flowable is an open-source business process management platform with AI agent integration and strong process governance.
It provides open standards compliance, enterprise-proven adoption, and vendor independence. However, it requires BPM
expertise and ongoing process management without built-in AI development tools. While excellent for process-centric AI
integration, it may require significant additional tooling for complete AI solutions.
**Choose Flowable when** you have BPM expertise, need open-source process management, and want to build custom AI
integrations around established BPM patterns. Your primary focus is on traditional business process management.
**Choose Swiss AI Hub when** you want process management designed for AI workflows from the ground up, with built-in AI
development tools and enterprise interfaces. You get the benefits of open-source with comprehensive AI capabilities
included, though development requires programming skills.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/3_core_components.md
title: "Core Components"
description: ""
---
# Core components
The Swiss AI Hub is a complete platform that includes infrastructure for agents, pipelines, and process automation. The
tier model described here is a recommended adoption path, not a set of separate software versions.
Organizations often struggle when they attempt complex automation projects without first building foundational AI
literacy. The tier model provides a structured approach that aligns with successful adoption patterns.
**Tier 1** provides secure LLM access to everyone in the organization. Through direct experience, users learn the
capabilities and limitations of the models, improve their prompt writing skills, and identify tasks in their daily work
where AI could be beneficial.
After a period of use, the friction of switching between work applications and a dedicated AI interface becomes a common
complaint. This leads to **Tier 1+**, which integrates the same AI capabilities directly into tools like Microsoft
Teams, Slack, and Outlook. This reduces workflow disruption and encourages wider adoption.
Users may then find that the generic models lack specific company context. For example, a model asked to evaluate a job
application would be unaware of an organization's internal hiring philosophy. This signals readiness for **Tier 2**.
**Tier 2** introduces specialized agents that are given access to organizational knowledge. An HR agent can be
configured to understand hiring criteria from employee handbooks. A finance agent can be taught the company's chart of
accounts to query financial systems. These agents use an organization's specific data to provide relevant, contextual
responses.
As agent usage grows, repetitive patterns of orchestration emerge. An employee might manually route a document to one
agent for analysis, then to a manager for review, and finally to another agent for a background check. This type of
multi-step, manual routing indicates a need for **Tier 3**.
**Tier 3** automates these patterns as formal processes. The system can be configured to ingest job applications, route
them to an HR agent for screening, request human review only for borderline cases, trigger background checks via
external systems, and notify the appropriate hiring managers. This allows human workers to focus on decision-making and
exception handling.
The platform includes all these capabilities from the start. The tier model addresses the question of when to adopt each
capability based on organizational readiness. The progression allows people time to adjust workflows, develop skills,
and build confidence in the system.
## Tier 1: Foundation for secure AI access

The primary user entry point is the Open-WebUI web interface, which supports text, document, and voice inputs.
All requests are processed through an API layer that handles authentication, verifies permissions for the requested
model, and logs the interaction for auditing. The request is then routed to the appropriate LLM, such as an OpenAI or
Google Gemini model, based on system configuration.
Responses from the LLM are streamed back to the browser using Server-Sent Events, allowing the user to see the text as
it is generated. This architecture maintains a responsive interface, even for queries that require longer processing
times.
A separate Admin UI, built with Nuxt, provides administrators with visibility into system usage, user management, and
model configuration. It allows them to monitor model use, set cost limits, and review audit logs.
All data, including user queries, LLM responses, conversation history, and system logs, is stored in the platform's
internal database. This ensures data remains within the organization's controlled infrastructure unless external model
access is explicitly configured.
## Tier 1+: Meeting users where they work

The limitation of a standalone web interface is the workflow disruption caused by context switching. Tier 1+ resolves
this by extending the platform's capabilities into the applications employees use daily. A user in Microsoft Teams can
summarize a conversation thread directly within the application, and the AI's response appears in the same channel.
::: details Supported Channels
The platform uses the Azure Bot Framework for integrations. Supported channels include:
- Alexa
- Email
- Facebook
- MS Teams
- Outlook
- Skype
- Slack
- Telegram
- WeChat
- Web
- ... and more
:::
Each integration adapts to the interaction patterns of the host tool. For example, an AI response in Slack might be
posted in a thread, while in Outlook it might assist with drafting email replies. The central API layer ensures that all
interactions, regardless of their origin, are subject to the same security, governance, and logging policies.
## Tier 2: From data to contextual intelligence

Tier 2 introduces capabilities for ingesting and structuring organizational data to provide context to AI agents. The
process starts with information sources like SharePoint document libraries.
A pipeline orchestrates the data processing workflow. It monitors connected sources for new or updated files and
triggers a processing sequence. Documents are parsed to extract not just text but also structural elements like headings
and tables. The content is then divided into semantically meaningful chunks based on topic changes or section breaks.
Each chunk is converted into a vector embedding using a model like Mistral.
::: tip
The pipeline described here is a default configuration. The SDK allows for the creation of custom pipelines to connect
to other sources (databases, APIs, FTP servers), customize document processing, and enrich data.
:::
These embeddings are stored and indexed in a vector database, which serves as the platform's knowledge base. This
enables semantic search, allowing agents to retrieve information based on meaning rather than exact keyword matches. The
system maintains a clear lineage from each embedding back to its source document for traceability and verification.
With a knowledge base in place, the platform can support multiple agents. Default agents might include a document
analyst or a data interpreter. Custom agents can be built with the SDK to handle organization-specific tasks, understand
internal terminology, or apply domain-specific reasoning.
The platform is also open towards the outside world using Model Context Protocol (MCP). Through MCP, agents from other
systems can securely access the platform, including its agents.
## Tier 3: Orchestrating business processes

Tier 3 introduces a process orchestration engine to automate multi-step workflows that involve AI agents, humans, and
external systems.
A dedicated Process UI allows users to visualize and interact with these workflows. It displays active processes as flow
diagrams, showing the current step, the responsible entity (human, agent or external system), and the subsequent steps.
When a process is triggered, the orchestration engine evaluates the first step. If the step requires document analysis,
the task is delegated to an AI agent. The agent's output is returned to the orchestrator, which then proceeds to the
next step. If a step requires human judgment, a task is created in the relevant user's workspace in the Process UI. The
interface provides the AI's analysis, the source document, and context for the decision. Once the human completes the
task, the process continues.
External systems can be integrated through connectors. Power Automate connections can trigger flows to update SharePoint
lists or send emails. An n8n integration enables communication with UiPath for robotic process automation tasks
involving legacy systems.
::: tip
The process described here is an example. The SDK can be used to create custom processes that connect to various
external and internal systems.
:::
The orchestration engine maintains the state of each process, managing timeouts and handling errors. If an external
system fails to respond, the process can be configured to route the task to a human for manual intervention. The API
layer is extended in this tier to manage conversation contexts that span multiple participants and long durations.
---
---
url: https://docs.ai-hub.bbv.ch/docs/1_vision_and_positioning/4_infrastructure_layers.md
title: "The Infrastructure Layers"
description: ""
---
# The Infrastructure Layers
::: warning
This documentation targets readers who want to fully understand the infrastructure components included in the platform
and the role they play. This level of understanding is not required to run or deploy the platform, but proves helpful if
you want to extend, scale, or modify it. The following sections translate the business-level view into technical
implementation details.
:::
## Tier 1: Core Infrastructure Components

The foundation begins with **OAuth2** handling authentication. When users access Open-WebUI or the Admin UI, OAuth2
validates their credentials against your organization's identity provider. This component was chosen over simpler
authentication methods because it integrates with existing enterprise systems like Azure AD or Keycloak without
requiring password synchronization or custom user management.
::: info
The architectural diagrams simplify certain aspects for readability. Multiple auxiliary databases that some tools
require are omitted, and some connections between components are simplified to avoid visual clutter. The diagrams
capture the conceptual relationships between infrastructure components rather than every technical detail.
:::
Behind the authentication layer, **Traefik** serves as the reverse proxy and API gateway. Every HTTP request passes
through Traefik, which routes traffic based on path patterns, handles TLS termination, and provides load balancing when
you scale components horizontally. Traefik's dynamic configuration capabilities allow the platform to register new
services without restarts, crucial for adding custom agents or additional UI components.
The **API** layer, built on FastAPI, provides more than simple request routing. It maintains WebSocket connections for
real-time streaming, manages session state for conversations, enforces rate limits per user, and transforms requests
between different component protocols. FastAPI was selected for its async capabilities, automatic OpenAPI documentation
generation, and excellent performance under concurrent load.
**LiteLLM** acts as the universal LLM gateway. Rather than implementing separate integrations for OpenAI, Anthropic,
Google, and local models, LiteLLM provides a unified interface. It handles retry logic when models are overloaded,
tracks token usage for cost allocation, manages different rate limits across providers, and converts between different
model-specific formats. The gateway pattern allows switching models without code changes, crucial for avoiding vendor
lock-in.
For model-specific features, **vLLM** provides high-performance inference for locally hosted models like Mistral or
DeepSeek. It uses PagedAttention for efficient memory management, allowing larger models to run on available hardware.
**Presidio** adds PII detection and anonymization, scanning text for sensitive data patterns before sending to external
models or storing in databases.
Storage infrastructure uses **SeaweedFS** for S3-compatible object storage and **MongoDB** for document storage.
SeaweedFS stores uploaded files, generated reports, and model artifacts with versioning and lifecycle policies. The
SeaweedFS Filer uses **etcd** as its metadata backend, enabling high-availability deployments with multiple Filer
instances. The platform exposes two interfaces: the **S3 API** at `s3.${DOMAIN}` with AWS signature authentication for
programmatic access, and the **Filer web UI** at `datalake.${DOMAIN}` via OAuth2 proxy for developers to browse and
debug files (requires the `AIHubSysAdmin` realm role). MongoDB persists conversation history, user preferences,
application data, and event history. These choices provide cloud-native storage patterns that work identically whether
deployed on-premise or in cloud environments.
The platform includes integrated AI tools that enhance the chat experience. **Open Terminal** enables code execution
when users ask a plain LLM model to analyze data or run calculations. The model writes Python code that executes in an
isolated per-user sandbox with common document libraries pre-installed; generated files (reports, spreadsheets, charts)
appear in the chat for download. **SearXNG** provides web search capabilities when users need current information,
aggregating results from multiple search engines while preserving privacy. **Playwright** scrapes content from websites
discovered through search, extracting full text when search snippets aren't sufficient. **MinerU** parses documents
users upload to the chat, extracting text and structure from PDFs, Word documents, and presentations while preserving
tables and formatting needed for accurate question-answering.
Observability starts from day one with **OpenTelemetry** collecting metrics, traces, and logs from every component. The
data flows to appropriate backends: metrics to Prometheus, traces to Jaeger, logs to Loki. This standardized
observability stack provides unified dashboards, distributed tracing across services, and centralized log aggregation
without vendor lock-in.
## Tier 1+: Integration Infrastructure

The **Azure Bot Framework** becomes the bridge between the platform and external communication channels. When a message
arrives from Teams, Slack, or Outlook, the Bot Framework normalizes it into a standard activity format, handles
channel-specific authentication, manages conversation context, and routes it to the appropriate handler in the API.
The Bot Framework was chosen over building separate integrations because it provides a single abstraction over multiple
channels. Channel-specific features like Teams adaptive cards or Slack blocks are handled through the same interface.
The framework manages conversation references, enabling the platform to send proactive messages back to users hours or
days after the initial interaction.
Connection between the Bot Framework and the platform API happens through webhook endpoints. The API implements the Bot
Framework protocol, accepting activities and returning appropriate responses. This loose coupling means the platform can
support other bot frameworks or direct integrations without architectural changes.
## Tier 2: Knowledge and Agent Infrastructure

**NATS** transforms the platform from request-response to event-driven architecture. Agents subscribe to event streams,
the API publishes user messages, and components communicate asynchronously without direct dependencies. NATS JetStream
provides persistent message queues, ensuring no events are lost during agent restarts. The choice of NATS over
alternatives like RabbitMQ or Kafka comes from its simplicity, embedded clustering, and excellent performance for the
small-message patterns common in agent communication.
The agent infrastructure supports multiple concurrent agents (Default 1-3, Custom 1-2 in the diagram). Each agent runs
as an independent service, subscribing to relevant NATS topics and publishing responses. Agents can construct their
state by replaying the event history, access the vector stores, and report telemetry through OpenTelemetry. This
microservice pattern allows agents to be developed and scaled independently, and updated without affecting other agents
or the platform.
**Dagster** orchestrates the data pipeline infrastructure. It schedules document ingestion from sources like SharePoint,
monitors pipeline health, manages dependencies between processing steps, and provides a web UI for pipeline monitoring.
Dagster's asset-based approach treats each processed document as a managed asset with lineage, versioning, and quality
checks. The choice of Dagster over alternatives like Airflow comes from its superior local development experience and
native Python integration.
Pipeline workers implement the actual document processing. They connect to the source, download documents to SeaweedFS
for processing, parse content using MinerU, generate embeddings using configured models, and store results in the vector
database. Workers scale horizontally, with Dagster distributing work across available instances.
**Milvus** provides vector storage for semantic search. It indexes high-dimensional embeddings, performs approximate
nearest neighbor searches, supports filtered searches combining vector and metadata queries, and scales to billions of
vectors through sharding. Milvus was selected over alternatives like Pinecone or Weaviate for its open-source nature,
on-premise deployment options, and excellent performance characteristics.
**Redis** provides fast state storage that agents use to persist data independent of events. Agents store state in Redis
when they need data to survive across conversation turns or be accessible to other agent instances. Redis was chosen for
its extremely fast in-memory performance and independence from Python processes, allowing agents written in any language
to access the same state store.
**Langfuse** provides AI-specific observability beyond OpenTelemetry. It captures LLM interactions with full prompts and
responses, traces RAG retrievals showing which documents were used, tracks per-trace and per-user costs, and provides
dataset management with experiment evaluation. Langfuse integrates with the existing OpenTelemetry infrastructure,
adding AI-specific context to standard traces. It supports Azure AD SSO for production access control.
**MCP (Model Context Protocol)** opens the platform to external tools. VSCode extensions can connect to running agents
for debugging, external AI systems can interact with our agents, and automation tools can submit work items to
processes. MCP uses JSON-RPC over WebSockets, providing a language-agnostic integration point. This protocol was
developed specifically for the Swiss AI Hub to enable tool integration without exposing internal APIs.
Throughout all tiers, components communicate through well-defined interfaces. HTTP/REST for synchronous requests, NATS
for asynchronous events, and WebSockets/SSE for real-time streaming. This polyglot approach uses the right protocol for
each use case rather than forcing everything through a single pattern.
The infrastructure choices reflect operational requirements discovered through production deployments. Every component
can run in containers, scales horizontally with load balancing, provides health checks for orchestration platforms,
exposes metrics for monitoring, and supports configuration through environment variables. These operational
characteristics matter as much as functional capabilities when building a platform meant for enterprise deployment.
## Tier 3: Process Orchestration Infrastructure

The **Process UI** introduces a new user interface paradigm designed for workflow interaction rather than conversation.
Built with Vue.js and connected via WebSockets, it provides real-time workflow visualization, task queues for human
participants, form builders for structured input, and audit trails for compliance. The separation from the chat UI
reflects the different interaction patterns: chat is exploratory and conversational, while process interaction is
structured and task-oriented.
**Process orchestration** (Process 1 in the diagram) runs as a separate service managing workflow state. It interprets
workflow definitions written in python, maintains process instance state in MongoDB, coordinates work distribution
through NATS, handles timeouts and error conditions, and provides process analytics. The orchestrator was built custom
rather than using existing BPMN engines because AI agent integration required capabilities beyond standard business
process patterns.
External integrations expand the platform's reach. **Power Automate (PA)** integration happens through the Microsoft
Graph API, triggering flows and receiving callbacks. **n8n** provides a self-hosted alternative for workflow automation,
connecting to hundreds of services through its node library. **UiPath** integration enables RPA bots to participate in
processes, handling legacy system interactions that lack APIs. These integrations use webhook patterns where possible,
falling back to polling when webhooks aren't available.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform.md
title: "Part II: The Platform Journey"
description: ""
---
# Part II: The Platform Journey
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start.md
title: "Quick Start: Your First 30 Minutes"
description: ""
---
@mhoegger
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start/1_prerequisites.md
title: "Prerequisites"
description: ""
---
# Prerequisites
This guide covers prerequisites for deploying the Swiss AI Hub platform.
::: tip Choose Your Deployment Type
The prerequisites differ based on your deployment type:
- **Production Deployment**: Deploy to a server with a real domain name and automatic SSL certificates
- **Local Deployment**: Run on your local machine with `127.0.0.1.nip.io` and self-signed certificates
Follow only the sections relevant to your deployment type.
:::
## Common Requirements (All Deployments)
These requirements apply to both production and local deployments.
### System requirements
#### Minimum specifications
- **CPU**: 8 cores
- **RAM**: 32 GB
- **Storage**: 200 GB free space
- **Network**: Stable internet connection for Docker image downloads
#### Recommended specifications
- **CPU**: 12+ cores
- **RAM**: 48+ GB
- **Storage**: 300+ GB SSD for improved database performance
- **Network**: High-bandwidth connection for faster initial setup
::: warning
The platform runs multiple services simultaneously (databases, vector stores, LLM proxies, web interfaces, processing
engines). Systems below minimum specifications will experience service failures or degraded performance.
:::
### Software requirements
#### Operating system
- Linux (Ubuntu 20.04+ recommended and tested)
- Any Docker-compatible Linux distribution
#### Required software
- **Docker**: Latest stable version
- **Docker Compose**: v2.0 or higher
- **sudo/root access**: Required for installation and configuration
#### Network configuration
- **Open ports**: 80 (HTTP), 443 (HTTPS)
- **Internet access**: Required for Docker image downloads and updates
#### Verification
Test your Docker installation:
```bash
docker --version
docker compose --version
docker run hello-world
```
All commands must complete successfully.
### Authentication Provider Setup
The platform requires an OAuth2/OpenID Connect identity provider for both production and local deployments. This guide
documents Azure Entra ID setup. Other providers (Google, Okta, Auth0) can be configured using similar patterns.
#### Azure Entra ID setup
Complete the following steps in the Azure Portal to prepare authentication:
**Step 1: Create app registration**
1. Navigate to **Azure Portal** → **Azure Active Directory** → **App registrations**
2. Click **"New registration"**
3. Configure the registration:
- **Name**: `Swiss AI Hub` (or your organization's naming convention)
- **Supported account types**: Select based on requirements
- `Accounts in this organizational directory only` (single tenant - recommended for most deployments)
- **Redirect URI**: Leave blank (will be configured in Step 5)
4. Click **"Register"**
**Step 2: Configure token version**
The platform requires access token version 2 for proper authentication.
1. Navigate to **"Manifest"**
2. Find the `requestedAccessTokenVersion` property
3. Change the value from `null` or `1` to `2`:
```json
"requestedAccessTokenVersion": 2
```
4. Click **"Save"** at the top of the manifest editor
::: warning
Access token version 2 is required for the platform to function correctly. Version 1 tokens are not supported and will
cause authentication failures.
:::
**Step 3: Configure API permissions**
1. Navigate to **"API permissions"** → **"Add a permission"**
2. Select **"Microsoft Graph"** → **"Delegated permissions"**
3. Add the following permissions:
- `openid` - Required for OpenID Connect authentication
- `profile` - Required for basic user profile information
- `email` - Required for email address
- `offline_access` - Required for refresh tokens
- `User.Read` - Required for user profile reading
4. Click **"Grant admin consent for [Your Organization]"**
5. Verify all permissions display **"Granted for [Your Organization]"** status
::: tip
The platform manages roles and user profiles locally — no Microsoft Graph API application permissions (such as
`User.ReadBasic.All`, `Directory.Read.All`, or `ProfilePhoto.Read.All`) are required. Only standard OIDC delegated
permissions are needed for authentication.
:::
**Step 4: Create client secret**
1. Navigate to **"Certificates & secrets"** → **"Client secrets"** → **"New client secret"**
2. Configure the secret:
- **Description**: Enter descriptive name (e.g., `Swiss AI Hub Secret`)
- **Expires**: Select expiration period (12-24 months recommended)
3. Click **"Add"**
4. **Immediately copy the secret value** to secure storage
5. Record this value as `[CLIENT_SECRET]` for deployment configuration
::: danger
The client secret value is displayed only once immediately after creation. If the value is lost, a new secret must be
created. Store in a password manager or secure vault.
:::
**Step 5: Configure app roles**
The `aihub` realm consumes exactly **two** app roles from the IdP, mapped by name from the Azure `roles` claim to
Keycloak realm roles of the same name:
- `AIHubAccess` — **required to log in.** Users without it are denied at the Keycloak login flow.
- `AIHubSysAdmin` — platform administrator; gates the oauth2-proxy admin tools (Dagster, SeaweedFS, Attu, Backup) and
Langfuse, and marks platform superusers.
::: tip Platform roles are not Azure app roles
`AIHubUser`, `AIHubAdmin`, `AIHubAgentUser`, … are **platform** roles — tenant-scoped and managed inside the platform,
not synced from Entra ID. Do **not** create Azure app roles for them; they have no effect at the Keycloak realm level.
The roles a new user receives are controlled by the `AIHUB_USER_SIGNUP_*` environment variables.
:::
Create the two app roles:
1. Navigate to **"App roles"** → **"Create app role"**
2. Create each of the following roles:
**Access role (required for login):**
- **Display name**: `AIHubAccess`
- **Allowed member types**: `Users/Groups`
- **Value**: `AIHubAccess`
- **Description**: `Required to log in to Swiss AI Hub`
**System Administrator role:**
- **Display name**: `AIHubSysAdmin`
- **Allowed member types**: `Users/Groups`
- **Value**: `AIHubSysAdmin`
- **Description**: `System administrator access to infrastructure tools (Dagster, SeaweedFS, Attu)`
::: tip
The `AIHubSysAdmin` role is required to access the Dagster pipeline orchestration dashboard, the SeaweedFS data lake
console at `datalake.${DOMAIN}`, and the Attu Milvus admin UI. Users with only `AIHubAccess` can still use the main
Swiss AI Hub interface and OpenWebUI.
:::
**Step 6: Configure SPA redirect URIs**
Single-Page Application (SPA) redirect URIs are required for the main web interface with multilingual support.
1. Navigate to **"Authentication"** → **"Add a platform"** → **"Single-page application"**
2. Add redirect URIs based on your deployment type:
**For Production:** Replace `your-domain.com` with your actual domain
```
https://your-domain.com/de/auth/callback
https://your-domain.com/en/auth/callback
https://your-domain.com/it/auth/callback
https://your-domain.com/fr/auth/callback
```
**For Local Deployment:** Use `127.0.0.1.nip.io`
```
https://127.0.0.1.nip.io/de/auth/callback
https://127.0.0.1.nip.io/en/auth/callback
https://127.0.0.1.nip.io/it/auth/callback
https://127.0.0.1.nip.io/fr/auth/callback
```
::: tip
You can add both production and local URIs to the same app registration for testing purposes.
:::
3. Configure token settings:
- Enable **Access tokens** (used for implicit flows)
- Enable **ID tokens** (used for implicit flows)
4. Click **"Configure"**
**Step 7: Configure web application redirect URIs**
Web application redirect URIs are required for integrated services (OpenWebUI, Dagster, Data Lake).
1. Navigate to **"Authentication"** → **"Add a platform"** → **"Web"**
2. Add redirect URIs based on your deployment type:
**For Production:** Replace `your-domain.com` with your actual domain
```
https://openwebui.your-domain.com/oauth/oidc/callback
https://dagster.your-domain.com/oauth2/callback
https://datalake.your-domain.com/oauth2/callback
https://attu.your-domain.com/oauth2/callback
```
**For Local Deployment:** Use `127.0.0.1.nip.io`
```
https://openwebui.127.0.0.1.nip.io/oauth/oidc/callback
https://dagster.127.0.0.1.nip.io/oauth2/callback
https://datalake.127.0.0.1.nip.io/oauth2/callback
https://attu.127.0.0.1.nip.io/oauth2/callback
```
3. Configure token settings:
- Enable **ID tokens** (used for hybrid flows)
4. Click **"Configure"**
::: warning Platform Type Distinction
The platform type configuration (SPA vs Web) is critical for OAuth2 flow selection:
- **SPA platform**: Language-specific callbacks (`/de/`, `/en/`, `/fr/`, `/it/`) use PKCE flow without client secret
- **Web platform**: Service callbacks (`openwebui`, `dagster`, `datalake`, `attu`) use authorization code flow with
client secret
Misconfigured platform types will cause authentication error
`AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type`. Ensure
redirect URIs are registered under the correct platform type.
:::
**Required Authentication Information**
After completing Azure setup, you should have:
- `[CLIENT_ID]` - Application (client) ID
- `[CLIENT_SECRET]` - Client secret value
- `[TENANT_ID]` - Directory (tenant) ID
You'll need these values during platform deployment configuration.
______________________________________________________________________
## Production Deployment Prerequisites
::: danger Only for Production Deployments
**Skip this entire section if you're testing locally.** These steps are only required when deploying to a server with a
real domain name.
:::
### DNS Configuration
Configure DNS records for your domain. The platform requires **seven subdomains** pointing to your server's public IP:
- `swiss-ai-hub.example.com` - main web interface
- `openwebui.swiss-ai-hub.example.com` - chat UI
- `dagster.swiss-ai-hub.example.com` - pipeline orchestration
- `datalake.swiss-ai-hub.example.com` - data lake console
- `litellm.swiss-ai-hub.example.com` - litellm proxy
- `attu.swiss-ai-hub.example.com` - Milvus vector database UI
- `traefik.swiss-ai-hub.example.com` - reverse proxy dashboard
Replace `swiss-ai-hub.example.com` with your actual domain. Create A records or CNAMEs for all seven subdomains pointing
to your server's IP address.
::: warning DNS Requirements for SSL
- DNS records must be globally accessible for Let's Encrypt SSL certificate provisioning
- The VM must be able to resolve its own domain names (internal DNS resolution)
- Configure nameservers correctly to avoid OAuth authentication timeouts
See [network requirements](../../3_deployment_guide/7_network_requirements/) for detailed DNS configuration and
troubleshooting.
:::
______________________________________________________________________
## Local Deployment Prerequisites
::: danger Only for Local Deployment
**Skip this entire section if you're deploying to production.** These steps are only required when deploying the
platform on your local machine.
:::
### Install mkcert
For local deployment with HTTPS support, you need to install **mkcert** to generate self-signed SSL certificates that
are trusted by your browser.
::: warning
Only use self-signed SSL certificates for local development. Never use them in production or public environments.
:::
**Linux (Ubuntu/Debian):**
```bash
sudo apt install libnss3-tools
wget -O mkcert https://dl.filippo.io/mkcert/latest?for=linux/amd64
chmod +x mkcert
sudo mv mkcert /usr/local/bin/
```
**Windows:**
```powershell
# Using Chocolatey
choco install mkcert
# OR using Scoop
scoop bucket add extras
scoop install mkcert
```
**macOS:**
```bash
brew install mkcert
```
**Verify Installation:**
```bash
mkcert -version
```
::: tip What is mkcert?
**mkcert** is a tool that generates locally-trusted SSL certificates without complex configuration. It automatically
installs a local Certificate Authority (CA) in your system trust store, so certificates it generates are trusted by your
browser.
:::
______________________________________________________________________
## Next steps
Proceed to [one-command deployment](../2_one_command_deployment/) to deploy the platform using the recorded
configuration values.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start/2_one_command_deployment.md
title: "One-Command Deployment"
description: ""
---
# One-Command Deployment: Launch Your AI Platform
The Swiss AI Hub platform deploys with a single Docker Compose command. This streamlined process gets your complete AI
infrastructure running in minutes, not hours.
## Quick Install
Run a single command to download, extract, and set up the platform:
```bash
curl -fsSL https://raw.githubusercontent.com/bbvch-ai/aihub-core/main/install.sh | bash
```
The installer auto-detects GPU hardware, downloads the correct release bundle, and generates all secrets. Afterwards,
edit `.env` and run `docker compose up -d`.
| Flag | Default | Description |
| ------------------- | ---------------- | ------------------------- |
| `--version VERSION` | latest | Pin to a specific release |
| `--gpu` | auto-detect | Force GPU bundle |
| `--cpu` | auto-detect | Force CPU-only bundle |
| `--dir PATH` | `./swiss-ai-hub` | Installation directory |
| `--help` | | Show usage |
**Examples:**
```bash
# Install with GPU bundle to a custom directory
curl -fsSL https://raw.githubusercontent.com/bbvch-ai/aihub-core/main/install.sh | bash -s -- --gpu --dir /opt/swiss-ai-hub
# Pin a specific version
curl -fsSL https://raw.githubusercontent.com/bbvch-ai/aihub-core/main/install.sh | bash -s -- --version v0.269.2
```
**Upgrading:** Run the installer again with the same `--dir`. It detects the existing installation, backs up your
`.env`, replaces bundle files, restores `.env`, and reports any new environment variables added in the release.
______________________________________________________________________
## Deployment Overview
::: tip Two Deployment Options
The Swiss AI Hub supports two deployment modes. Follow the same steps for both, using the appropriate commands for your
deployment type:
- **Production Deployment**: Deploy to a server with a real domain name (e.g., `swiss-ai-hub.yourcompany.com`)
- Uses the CPU or GPU release bundle from GitHub Releases
- Uses Let's Encrypt for automatic SSL certificates
- Requires DNS configuration pointing to your server
- **Local Deployment**: Run on your local machine for development/testing
- Uses `infra/docker-compose.local.yml` from the repository
- Uses self-signed SSL certificates (mkcert)
- Uses `127.0.0.1.nip.io` domain (automatically resolves to localhost)
Each step below shows commands for both deployment types. Simply follow the commands that match your chosen deployment
mode.
:::
______________________________________________________________________
## Step 1: Get Deployment Files
**For Production:**
Download the latest release bundle from [GitHub Releases](https://github.com/bbvch-ai/aihub-core/releases). Each release
provides two self-contained bundles:
- `swissaihub-.tar.gz` — CPU-only deployment
- `swissaihub--gpu.tar.gz` — GPU-enabled deployment (includes vLLM, GPU-accelerated inference)
```bash
# Set the version you want to deploy
VERSION="v0.266.0" # Replace with the desired version
# Download and extract the release bundle (CPU example)
mkdir swiss-ai-hub && cd swiss-ai-hub
curl -L "https://github.com/bbvch-ai/aihub-core/releases/download/${VERSION}/swissaihub-${VERSION}.tar.gz" \
| tar -xz
# For GPU-enabled deployment, use this instead:
# curl -L "https://github.com/bbvch-ai/aihub-core/releases/download/${VERSION}/swissaihub-${VERSION}-gpu.tar.gz" \
# | tar -xz
```
The release bundle contains everything needed to deploy: `docker-compose.yml`, all service configuration files, an
`.env.template`, and the `setup-env.sh` script.
**For Local Deployment:**
```bash
# Clone the repository
git clone https://github.com/bbvch-ai/aihub-core.git
cd swiss-ai-hub
# Generate SSL certificates with mkcert
mkcert -install # Install local CA (only needed once)
make local-cert
```
::: tip What is nip.io?
The `*.127.0.0.1.nip.io` domain automatically resolves to your localhost (127.0.0.1), providing wildcard DNS resolution
without needing to modify your hosts file. This allows subdomain-based routing in local development.
:::
______________________________________________________________________
## Step 2: Configure Environment Variables
### Generate Environment Configuration
**For Production (release bundle):**
The release bundle includes a `setup-env.sh` script that generates a `.env` file from the included `.env.template`. It
automatically creates unique secrets for all database passwords, tokens, and signing keys:
```bash
# Generate .env with auto-generated secrets
./setup-env.sh
```
::: details What does setup-env.sh do?
The script reads `.env.template` and replaces all `REPLACE_WITH_*` placeholders with cryptographically secure random
values. Each placeholder gets its own unique secret. The script requires only `openssl` and `bash` — no Python or other
dependencies needed.
```bash
# Options:
./setup-env.sh # Default: .env.template → .env
./setup-env.sh -t custom.template -o out.env # Custom paths
./setup-env.sh --force # Overwrite existing .env
```
:::
**For Local Deployment:**
```bash
cp .env.dev .env
```
### Configure Remaining Values
After generating your `.env` file, review it and fill in the values that require manual configuration:
**Critical Values to Replace:**
1. **Domain** — Set `DOMAIN` to your production domain (e.g., `swiss-ai-hub.yourcompany.com`) or `127.0.0.1.nip.io` for
local testing
2. **Authentication Values** (from Prerequisites):
- `REPLACE_WITH_YOUR_CLIENT_ID` → Your Azure App Registration Client ID
- `REPLACE_WITH_YOUR_CLIENT_SECRET` → Your Azure App Registration Client Secret
- `REPLACE_WITH_YOUR_TENANT_ID` → Your Azure Tenant ID
3. **AI Model Access** (Swiss LLM Cloud — required for non-GPU deployments):
- `REPLACE_WITH_SWISS_LLM_CLOUD_URL` → Your Swiss LLM Cloud text generation endpoint
- `REPLACE_WITH_SWISS_LLM_CLOUD_KEY` → Your Swiss LLM Cloud API key
- Configure the remaining endpoint pairs for embedding, reranking, whisper, and OCR
4. **Expert Escalation** (optional — for expert-in-the-loop features):
- `REPLACE_WITH_TEAMS_CHANNEL_ID` → Your Teams channel ID (format: `19:xxx@thread.tacv2`)
- `REPLACE_WITH_TEAMS_TENANT_ID` → Your Azure AD tenant ID
- `REPLACE_WITH_TEAMS_BOT_ID` → Your Azure Bot Service application ID
::: info Simplified Configuration
Internal service endpoints (like database URLs, message queues, etc.) are hardcoded in the Docker Compose files. You
only need to configure credentials and external service connections. All database passwords, tokens, and signing keys
are auto-generated by `setup-env.sh`.
:::
### Environment Validation
Before deployment, verify your configuration:
```bash
# Check for placeholder values that still need replacement
grep -n "REPLACE_WITH" .env
```
This should return no results if all placeholders are replaced.
______________________________________________________________________
## Step 3: Deploy the Platform
### Launch All Services
Deploy the complete platform with one command:
**For Production (release bundle):**
```bash
docker compose up -d
```
This command will:
- Download all necessary Docker images
- Create required networks and volumes
- Start all platform services in the correct order
- Configure service discovery and communication
### Monitor Deployment Progress
Watch the deployment progress:
```bash
# See all services starting
docker compose logs -f
# Check service health status
docker compose ps
```
**Expected Services:** The platform includes these core services:
- **Web Interface** (swiss-ai-hub-web)
- **API** (swiss-ai-hub-api)
- **Authentication** (auth services)
- **Databases** (FerretDB, PostgreSQL, Valkey)
- **Vector Database** (Milvus)
- **LLM Proxy** (LiteLLM)
- **Document Processing** (MinerU)
- **Observability** (Langfuse)
- **Message Queue** (NATS)
- **Storage** (SeaweedFS)
### Wait for Service Initialization
Initial startup takes 3-5 minutes while services initialize. All services should show "healthy" status:
```bash
# Wait for healthy status
docker compose ps --format "table {{.Name}}\t{{.Status}}"
```
______________________________________________________________________
## Step 4: Verify Successful Deployment
### Access the Platform
1. **Make sure your User that you test with has the roles "AIHubAdmin" and "AIHubSysAdmin" assigned in the Azure
Enterprise Application**
2. **Web Interface:**
- Production: `https://your-domain.com`
3. **Expected Login Flow:**
- Redirects to Azure authentication
- After login, returns to Swiss AI Hub interface
- Should see the main dashboard
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start/3_first_login.md
title: "First Login"
description: ""
---
@mhoegger
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start/4_your_first_conversation.md
title: "Your First Conversation"
description: ""
---
@SkypeForBusiness [WIP]
# Your First Conversation
There are two ways to begin your first conversation with an agent.
The first way is through the chat interface located under OpenAI in the services selection menu in the top left, this is
the main way of communicating with agents.
The second way is through the agent itself, located under agents in the services selection menu.
Both approaches work, but most users will use the chat interface.
## Chat Interface
As a chat interface we use [Open WebUI](https://github.com/open-webui/open-webui), an open source and feature rich
interface. After logging in with Azure, begin your first conversation by typing your message and hitting enter.
Switching the model or agent is as easy as selecting them in the top left of your chat. You can even set one as your
default. Here it is also possible to set up a temporary chat by selecting the toggle at the bottom of the models list.
Here is a quick video showing you how to get to the chat:
Use the chat as you would any other. A detailed overview of all chat features can be found
[here](../../10_chat_ui/1_feature_overview/).
## Agent
In the agent overview select the agent you want to chat with and then select the Chat tab at the top of the new window.
Here you can enter a message and begin talking to the agent. Once having started your conversation you will be able to
see the whole trace of what the agent is doing.
See it in action here:
______________________________________________________________________
Next, let's explore what you can learn from these interactions.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start/5_your_first_insights.md
title: "Your First Insights"
description: ""
---
@SkypeForBusiness [WIP]
# Your First Insights
The Swiss AI Hub Platform provides access to agents that can answer questions and retrieve information. Let's look at
what insights you can gain from interactions with these agents.
## Traceability
After starting a conversation with any agent, you can review all the events generated during the interaction.
The tracing service shows all interactions between agents and users. You can examine any interaction to see exactly what
the agent did and how it generated the answer. You can also open this view directly from the chat interface by selecting
the tracing icon underneath the agent's answer. This opens a window from the right with the same view as the service
page, allowing you to analyze an interaction without leaving the chat.
## Document Retrieval
You can view the document parts a RAG agent used to answer your question directly from the chat interface. This allows
you to fact-check the agent's answer against the document contents.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/1_quick_start/6_what_just_happened.md
title: "What Just Happened?"
description: ""
---
# What Just Happened?
The Swiss AI Hub Platform is straightforward to set up and run. This introduction covers the basics, but the platform
offers many more features. You can start using it as is with no modification, or explore other topics in depth. The
platform is fully functional and ready for use.
If any parts of this introduction are unclear, revisit them at your own pace. The following chapters cover more features
and capabilities in detail.
## Where to go next?
Here are some options on where you can go next, depending on what you are interested in.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/2_architecture.md
title: "Platform Architecture"
description: "Technical architecture of the Swiss AI Hub platform — C4 model, communication protocol, network isolation, runtime scenarios, and deployment topology."
---
# Platform Architecture
This section documents the technical architecture of Swiss AI Hub. The content here is a deep dive — for the
marketing-level platform overview, see [Core Components](../../1_vision_and_positioning/3_core_components/) and
[Infrastructure Layers](../../1_vision_and_positioning/4_infrastructure_layers/) under Vision & Positioning.
The architecture is modelled with [LikeC4](https://likec4.dev), rendered as interactive diagrams via web components.
Source files live in `docs/likec4/` and are regenerated automatically by the docs dev server. Use the C4 hierarchy to
navigate:
- **[System Context](./1_system_context/)** — L1: people and external systems that interact with Swiss AI Hub
- **[Containers](./2_containers/)** — L2: the platform's first-party packages, infrastructure, and how they connect;
includes tier breakdowns
- **[Swiss AI Agent Protocol](./3_swiss_ai_agent_protocol/)** — Control / Display event model over NATS
- **[Network Isolation](./4_network_isolation/)** — Docker network zones and traffic policy
Coming soon as the LikeC4 model expands:
- **Runtime Scenarios** — dynamic views of chat streaming, document ingestion, bot-in-the-loop, agent discovery
- **Deployment Topology** — prod deployment with per-class agent instances, OIDC middleware, host-network OpenWebUI
- **Component Internals** — L3 component views for each first-party package (these will live on the per-package pages
under [Code Deep Dive](../../6_code_deep_dive/))
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/2_architecture/1_system_context.md
title: "System Context"
description: "C4 Level 1 — people and external systems that interact with Swiss AI Hub."
---
# System Context
The system context view answers the broadest question: who interacts with Swiss AI Hub, and what external systems does
it integrate with? It is C4 Level 1 — the platform appears as a single box, with no internal detail. The point is to
draw a clean boundary: everyone and everything that *talks to* the platform appears here; everything *inside* it is
deferred to the [Containers](../2_containers/) view.
Read it as two rings around the central system. Above are the **people** who use the platform, each in a distinct role
with a distinct surface. Around the edges are the **external system categories** the platform integrates with — and a
key idea of Swiss AI Hub is that these are *pluggable capabilities*, not fixed vendor choices. The platform ships
preconfigured with sensible defaults but federates, routes, or adapts to whatever conforming implementation a customer
brings.
## Actors
- **Enterprise User** — Any tenant member. Covers both regular users and tenant administrators, who share the same
surface (Admin UI + OpenWebUI + Main API) with feature visibility gated by tenant-scoped roles.
- **System Administrator** — Cross-tenant platform admin. Holds the `AIHubSysAdmin` realm role; uses the separate
sysadmin plane (`sysadmin.${DOMAIN}`) to manage tenants, user-tenant assignments, and platform-level configuration.
- **Collaboration Channel User** — Subset of tenant users who interact with Swiss AI Hub agents from collaboration
channels (Slack, Teams, Webex, email) rather than the dedicated web UI.
- **Platform Operator** — Platform DevOps / SRE engineer. Holds the `AIHubSysAdmin` realm role (the gate in front of the
oauth2-proxy admin tools); consumes observability and operational surfaces (Dagster, Langfuse, Attu, Backup, SeaweedFS
Filer).
## External integrations
Each external box represents a *pluggable integration capability*, not a specific vendor. The platform ships
preconfigured for the examples listed but supports any conforming implementation through its respective gateway /
adapter:
- **Identity Provider** — Federated into our Keycloak realm via OIDC/SAML. Examples: Entra ID, Okta, on-prem LDAP/AD,
Google, GitHub.
- **LLM Provider** — Configured per-deployment in the internal LiteLLM gateway. All tenants in a deployment share the
same model roster. Examples: Swiss LLM Cloud, OpenAI, Anthropic, Azure OpenAI, Gemini.
- **Document Source** — Synced via Rclone (70+ supported backends) or direct integration. Examples: SharePoint (direct
MS Graph), OneDrive, Google Drive, Azure Blob, S3, Dropbox.
- **Collaboration Platform** — Routed via the Microsoft Agents SDK. Examples: Slack, Microsoft Teams, Webex, email.
- **Observability Sink** — Customer-managed trace/log receiver via OTEL exporter. Examples: SigNoz, Grafana Cloud,
Honeycomb.
- **Notification Target** — One-way alert destination via Apprise (80+ supported targets). Examples: Slack channels,
Discord webhooks, email, PagerDuty.
- **External MCP Tools** — Customer-exposed MCP servers consumed by agents as tools. Examples: domain-specific APIs,
internal CRMs, ticketing systems.
For deeper integration mechanics, see the [Container view](../2_containers/) where each external category maps to
specific containers that bridge it.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/2_architecture/2_containers.md
title: "Containers"
description: "C4 Level 2 — first-party application containers plus the central infrastructure they connect through, organized by tier."
---
# Containers
The container view zooms into the Swiss AI Hub box from [System Context](../1_system_context/) and shows what's inside.
In C4 terminology a *container* is a separately deployable, independently runnable unit — an application process, a
database, a message broker — not necessarily a Docker container (though here they usually map one-to-one).
Swiss AI Hub runs roughly 33 containers in production. Showing all of them on one diagram would be unreadable, so this
page is organized in two layers: a **headline overview** of just the parts a developer thinks about daily, followed by
**focused tier views** that each zoom into one functional layer of the platform. Pick the view that matches the question
you're asking.
::: tip
Click any diagram to open the interactive viewer — you can pan, zoom, follow relationships, and jump between views.
:::
## Overview
This headline view shows the nine first-party application containers — the packages we build — plus the NATS event bus
that connects them. It deliberately omits the supporting infrastructure (databases, gateways, observability) so the
application topology stands out: who talks to whom, and how the event spine ties the backend services together.
If you're new to the platform, start here. It's the mental model to hang everything else on.
## Application tier
The first-party packages plus OpenWebUI — everything with custom application logic. This is the tier most contributors
work in. The view focuses purely on how application containers reach each other; the infrastructure they all depend on
(databases, LLM gateway, identity) lives in the tiers below to keep this diagram legible.
Note that the Agent Runtime appears as a single logical box here but is deployed as one container *per agent class* in
production. The Process Engine is not a container at all — it runs embedded inside the API Gateway.
## LLM / AI Inference tier
Every model call in the platform — chat, embeddings, reranking, speech, OCR — funnels through the **LiteLLM gateway**.
This single chokepoint is what makes the platform model-agnostic: switching from Swiss LLM Cloud to OpenAI to a local
GPU model is a configuration change in LiteLLM, not a code change anywhere else. Presidio sits in the path to redact PII
before requests reach external providers, and MinerU, vLLM, and Speaches provide local parsing, GPU inference, and
speech respectively.
## Data tier
The platform's stateful stores, each chosen for a specific job: PostgreSQL for relational data, FerretDB for documents,
Valkey for ephemeral agent state, Neo4j for graph-based memory, Milvus for vectors, ClickHouse for analytics, and
SeaweedFS for S3-compatible object storage. The view also shows their internal dependencies — Milvus and SeaweedFS both
use etcd for metadata, FerretDB runs on its own backing Postgres, and ClickHouse offloads to S3 — which is why "the
database tier" is more interconnected than it first appears.
## Eventing tier
NATS / JetStream is the spine of the platform — the single box that nearly every application container publishes to or
subscribes from. The Swiss AI Agent Protocol runs over it, distinguishing durable **Control Events** (workflow state, on
JetStream) from ephemeral **Display Events** (observability, on NATS Core). This separation is what lets the chat UI
visualize an agent's reasoning in real time without interfering with the agent's actual execution. The protocol itself
is documented in [Swiss AI Agent Protocol](../3_swiss_ai_agent_protocol/).
## Identity & Edge tier
Everything at the network boundary. Traefik is the single ingress point, terminating TLS and routing every `*.${DOMAIN}`
subdomain to the right service. Keycloak is the identity broker that federates customer identity providers and issues
the platform's OIDC tokens and realm roles. A set of oauth2-proxy instances forms a uniform OIDC gate in front of the
operator UIs (Dagster, Backup, Attu, SeaweedFS Filer), and pgbouncer pools database connections for Dagster. The
application containers these front are shown in their own tiers — this view is about the edge machinery itself.
## Observability tier
The OTEL Collector aggregates OpenTelemetry traces and logs from every application container and forwards them to
Langfuse, which adds AI-specific observability on top — full prompt/response capture, per-trace cost tracking, and RAG
retrieval tracing. By default everything stays inside the deployment; the collector can also be configured to export to
a customer-managed sink (SigNoz, Grafana Cloud, Honeycomb). Langfuse's own data dependencies (ClickHouse, Postgres,
Valkey, SeaweedFS) belong to the Data tier.
## Utility tier
Auxiliary services that support the application tier without being core to it: SearXNG for web search, Open Terminal as
the code-execution sandbox for OpenWebUI (plain LLM models; per-user isolation, downloadable file output), Playwright
for browser automation, and Attu as a Milvus admin console for operators. These are consumed mostly by OpenWebUI (as
agent tools) and by operators.
## Package-centered views
The tier views above slice the platform horizontally — by functional layer. The
[Package-Centered Views](../5_package_views/) slice it the other way: one diagram per first-party package, centered on
that package with all its neighbours, for developers about to work inside a specific package.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/2_architecture/3_swiss_ai_agent_protocol.md
title: "Swiss AI Agent Protocol"
description: ""
---
# Swiss AI Agent Protocol
The platform's infrastructure layers provide the core components, such as the message bus and databases. The Swiss AI
Agent Protocol defines the set of rules and contracts that makes them work together as a coherent system. It is an
internal, event-driven model that governs how agents operate, manage state, and communicate with the platform. Every
agent built with the SDK adheres to this protocol, and every component that interacts with an agent, from the API to the
user interface, relies on it.
The following describes the abstract rules of this protocol, not the specific Python implementation. These principles
form the foundation for building agents that are transparent, scalable, and resilient by design.
## Why a protocol? Engineering for autonomous AI
AI operations are often multi-step, asynchronous processes that can involve multiple models, long-running tasks, and
human interaction. A traditional architecture where internal components communicate through synchronous APIs can create
tight coupling. This makes the system difficult to scale, observe, and modify, as a change in one component can have
cascading effects on others.
The Swiss AI Agent Protocol addresses these challenges by defining a standardized, event-driven contract for
asynchronous communication. It provides a common language for all platform participants, ensuring that interactions are
predictable and decoupled.
### Beyond chatbots: A world of autonomous agents
Agents within the platform are not limited to conversational, request-response tasks. They are designed as persistent,
autonomous entities that can operate independently of any direct user interaction. An agent might monitor a data source,
manage a business process that takes days to complete, or perform scheduled analysis.
Because an agent's operational lifetime is not tied to a single user query, it can run for minutes, hours, or even
months. This persistent and autonomous nature introduces a level of complexity that requires a formal communication
protocol to manage state, ensure security, and provide clear observability over long periods.
### The need for a granular, interoperable contract
The protocol is a granular contract where every meaningful action, thought, or state change is defined as a distinct
event. This approach provides a high-resolution view of an agent's operations, which is necessary for detailed tracing
and debugging.
This granularity also enables interoperability. The platform can translate its rich internal event stream to support
external protocols like the OpenAI API. An adapter can listen for specific events, such as `ChunkEvent` or `StopEvent`,
and reformat them as OpenAI-compatible Server-Sent Events. This process discards any protocol-specific information that
the external system does not understand, allowing clients to interact with the platform using familiar standards.
## The protocol participants and their roles
The protocol defines the communication rules for a set of participants, not just the agents. Each participant has a
distinct role and interacts with the event stream in a specific way.
### The agent
The agent is the autonomous worker of the ecosystem. Its role is to execute business logic and complex reasoning. It
consumes `Control Events` to start or continue its work. It produces new `Control Events` to advance its internal
workflow or delegate tasks, and a rich stream of `Display Events` to report its status, reasoning, and results.
### The API gateway
The API Gateway acts as the secure entry point for all external clients. Its role is to translate between external
communication formats, such as HTTP, and the internal protocol. The API Gateway is the exclusive producer of initial
`Control Events` that originate from outside the system. It validates user identity and permissions before publishing an
event to the message bus.
### The frontend
The user interface provides a real-time view into an agent's operations. It is primarily a consumer of `Display Events`.
The frontend uses this stream of events to render the agent's activity, such as showing streaming text or internal
reasoning steps. It does not produce events directly but initiates actions by sending requests to the API Gateway.
### The process orchestrator
The process orchestrator manages high-level business processes that may involve multiple agents, human tasks, and
external systems. It functions as a specialized agent. Its protocol interaction consists of consuming `Control Events`,
often the `StopEvent` from a worker agent, and then producing a new `Control Event` to trigger the next participant in
the process.
The following diagram illustrates a typical interaction flow between these participants.
```mermaid
sequenceDiagram
participant User
participant Frontend
participant API Gateway
participant Event Bus
participant Agent
User->>+Frontend: Sends message
Frontend->>+API Gateway: POST /chat/completions
API Gateway->>+Event Bus: Publishes UserMessageEvent (Control Event)
API Gateway-->>-Frontend: HTTP 200 OK (acknowledges request)
Event Bus->>+Agent: Delivers UserMessageEvent
Agent->>+Event Bus: Publishes ChunkEvent (Display Event)
Event Bus->>+Frontend: Streams ChunkEvent
Frontend->>User: Displays "Hello,"
Agent->>+Event Bus: Publishes ChunkEvent (Display Event)
Event Bus->>+Frontend: Streams ChunkEvent
Frontend->>User: Displays " how can I help?"
Agent-->>-Event Bus: Publishes StopEvent (Control & Display Event)
Event Bus->>+Frontend: Streams StopEvent
Frontend-->>-User: Signals completion of response
```
## Scoping and security: the hierarchical context
To manage long-running interactions and ensure security, every event within the protocol is scoped into a three-level
hierarchy. This structure is encoded directly into the topic of every event published on the message bus, allowing for
granular control over visibility and access.
### The three scopes of an event
- **`Run` Context**
- **Definition:** The most granular scope. A "Run" is a single, traceable execution of a workflow, defined by the
sequence of events between a `StartEvent` and a corresponding `StopEvent` or `ExceptionEvent`.
- **Purpose:** It provides a unique identifier for a single, complete agent operation. This scope is essential for
tracing and debugging, as it isolates the events of one specific task.
- **`Display` Context**
- **Definition:** A scope designed to group multiple `Run`s together for presentation in a user interface. It can span
multiple agents.
- **Purpose:** It manages what an end-user or observer sees. When an agent delegates work to another agent, it can
choose to pass on its `Display` context. If it does, the frontend, which subscribes to this context, will show the
events from both agents as part of a single, seamless interaction. If the primary agent creates a new `Display`
context for the delegated task, that work becomes "hidden" from that specific UI view.
- **`Thread` Context**
- **Definition:** The highest-level scope, which groups multiple `Display` contexts and `Run`s that belong to a
single, overarching goal or conversation.
- **Purpose:** It maintains the long-term history and state of a process. A chat conversation is a `Thread`. An
autonomous agent processing invoices for an entire month might operate within a single `Thread` for that month's
work.
The following diagram illustrates this hierarchical structure.
```mermaid
graph TD
subgraph User Scope
User("👤 User")
end
subgraph Platform Scope
subgraph Thread [Thread: a-b-c]
direction LR
subgraph Display Context 123
direction TB
RunA1("Run A1
(Start -> ... -> Stop)")
RunB1("Run B1
(Start -> ... -> Stop)")
end
subgraph Display Context 456
direction TB
RunC1("Run C1
(Start -> ... -> Stop)")
end
end
subgraph Agents
AgentA("🤖 Agent A")
AgentB("🤖 Agent B")
AgentC("🤖 Agent C")
end
end
User -- has access to --> Thread;
AgentA ==> RunA1;
AgentB ==> RunB1;
AgentC ==> RunC1;
style Thread fill:#e6f7ff,stroke:#91d5ff,stroke-width:2px
style User fill:#fffbe6,stroke:#ffd666,stroke-width:2px
```
::: details Explanation
- The entire interaction is contained within a single `Thread`, to which the `User` has access.
- Within this thread, there are two separate `Display` contexts. A user observing `Display Context 123` would see a
seamless flow of activity from both `Run A1` (executed by Agent A) and `Run B1` (executed by Agent B). This is typical
when one agent delegates a task to another.
- The user would not see any events from `Run C1`, as it belongs to a different `Display` context, effectively isolating
that operation from this particular view.
:::
### Security through scoping
This hierarchical scoping is the foundation of the platform's security model. Access is granted at the **`Thread`**
level. A user can only observe events from threads of which they are a member.
Agents can also operate in threads without any human members. In this case, only administrators with sufficient
permissions for the participating agents can observe the events within that thread. This ensures that autonomous,
backend processes remain secure and isolated.
## The language: a library of standardized events
### The event: an immutable record of fact
The fundamental unit of communication in the protocol is the **Event**. An event is a structured, typed, and immutable
data record representing a fact that has occurred. Each communication consists of two distinct parts:
1. **The Topic:** The address on the message bus where the event is published. The topic provides the event's full
context, including its scope (Thread, Display, Run) and origin.
2. **The Payload:** A self-contained JSON object containing the specific data for that event.
The system combines this contextual topic and data payload to form a complete, understandable communication.
#### The Topic Structure
The topic is a hierarchical string that provides routing and scoping information. Every event is published to a topic
that follows this structure.
::: details Example
**Topic:**
`agent.RAGAgent.wiki_agent.t948a201-....d135bfc9-....r4fg68bb-....display_event.UserMessageEvent.r4fg68bb-...`
| Segment | Example Value | Description |
| :------------ | :-------------- | :--------------------------------------------------------- |
| `agent_class` | `RAGAgent` | The class of the agent publishing or being targeted. |
| `agent_id` | `wiki_agent` | The unique identifier of the specific agent instance. |
| `thread_id` | `t948a201-...` | The identifier for the high-level thread context. |
| `display_id` | `d135bfc9-...` | The identifier for the UI-facing display context. |
| `run_id` | `r4fg68bb-...` | The identifier for the specific run context. |
| `event_type` | `display_event` | The primary category (`control_event` or `display_event`). |
| `event_name` | `ChunkEvent` | The specific name of the event. |
| `event_id` | `r4fg68bb-...` | A unique identifier for this single event instance. |
:::
##### The Event Payload
The payload is the data associated with the event. Its structure is defined by the event type. All events, however,
share a common set of core attributes.
::: details JSON
**Example `UserMessageEvent` Payload:**
```json
{
"event_id": "e423",
"created_at": 1755015355940833270,
"_event_name": "UserMessageEvent",
"_parent_event_names": [
"UserMessageEvent",
"StartEvent",
"ControlAndDisplayEvent",
"ControlEvent",
"DisplayEvent"
],
"display_name": { "en": "User Request", "de": "Benutzeranfrage", "...": "..." },
"display_description": { "en": "The agent has received a message...", "...": "..." },
"locale": "de",
"user": {
"id": "cc4af21b-981a-4a76-826d-e722715082e0",
"name": "Joel Barmettler",
"...": "..."
},
"messages": [
{ "role": "system", "...": "..." },
{ "role": "user", "...": "..." }
]
}
```
:::
| Core Payload Attribute | Description |
| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
| `event_id` | A unique identifier for the event, matching the one in the topic. |
| `created_at` | A nanosecond precision timestamp marking when the event was created. |
| `_event_name` | The specific class name of the event. This is used by subscribers to deserialize the payload into the correct data object. |
| `_parent_event_names` | A list of the event's parent types in its inheritance hierarchy, which allows for filtering and routing based on broader categories. |
| `display_name` | A human-readable name for the event, with support for multiple locales. |
| `display_description` | A human-readable description of what the event represents, with support for multiple locales. |
The remaining fields in the payload are specific to the event's type. For this `UserMessageEvent`, the payload includes
`locale`, `user` identity, and the `messages` history. Other event types will have different data fields relevant to
their purpose.
### The core categories: Control vs. Display
To ensure that agent workflows are predictable and that observation does not interfere with execution, the protocol
strictly categorizes every event. Each event's payload contains a `_parent_event_names` list, which declares whether it
belongs to the `ControlEvent` or `DisplayEvent` category, or both.
#### Control Events (instructions)
Control Events drive the workflow and cause state changes. The protocol dictates that **only a `Control Event` can
trigger the execution of an agent's step.** They represent commands, completed tasks, or responses that require the
agent to proceed to the next logical operation.
- **Example:** A `UserMessageEvent` is a `Control Event` because it instructs an agent to start a new run. A
`HumanInTheLoopResponseEvent` is a `Control Event` because it provides the necessary input for a paused workflow to
continue.
#### Display Events (commentary)
Display Events are purely informational and are intended for observation by users or monitoring systems. The protocol
mandates that **a `Display Event` must never influence the logical flow of an agent's workflow.** Their purpose is to
provide a real-time narrative of the agent's internal state, reasoning process, or partial results. This separation
ensures that a failure in a UI or logging component cannot break the agent's core logic.
- **Example:** A `ThoughtEvent` provides a window into the agent's reasoning. A `ChunkEvent` streams a piece of a text
response to the user interface.
Some events can serve both functions. For example, a `StopEvent` is a `Control Event` because it terminates the workflow
run, but it is also a `Display Event` because the user interface needs to be informed that the process is complete. Such
events adhere to the rules of both categories.
### The core event library
The protocol defines a standard library of event types for common operations in AI and agentic systems. While developers
can create custom events to handle domain-specific logic, this core library provides the essential building blocks for
managing workflow lifecycles, interacting with users, and ensuring observability.
The following tables serve as a reference for the most common events, categorized by their function.
#### Lifecycle events
These events manage the state of a single workflow `Run`.
| Event | Category | Purpose |
| :------------------- | :---------------- | :---------------------------------------------------------------------------------- |
| **`StartEvent`** | Control & Display | Signals the beginning of a new workflow run and carries its initial context. |
| **`StopEvent`** | Control & Display | Signals the successful completion of a workflow run. No further steps are executed. |
| **`ExceptionEvent`** | Control & Display | Signals that an unrecoverable error occurred during a run, causing it to terminate. |
#### User interaction events
These events handle direct input from human users.
| Event | Category | Purpose |
| :--------------------- | :---------------- | :--------------------------------------------------------------------------------------------------------------------------- |
| **`UserMessageEvent`** | Control & Display | A specialized `StartEvent` that is triggered by a user sending a message. It contains the message history and user identity. |
::: warning `UserMessageEvent` is a Chat UI Contract
`UserMessageEvent` is the canonical entry point for **chat interfaces** (OpenWebUI, Teams, Slack, WebChat). Every chat
UI that wants to drive an agent must know how to publish and render it, so its payload must stay minimal — every field
added to `UserMessageEvent` (or to a subclass riding on it) raises the bar for every chat client in the ecosystem.
If your agent needs a richer entry payload and the publisher is **not** a generic chat UI (for example a custom domain
front-end that runs its own selection flow, or another agent delegating via `AgentInTheLoop`), subclass `StartEvent`
directly instead of `UserMessageEvent`, and have the agent declare `event: UserMessageEvent | YourStartEvent` on the
relevant steps. The RAG agent follows this pattern with `RAGStartEvent`, which carries a `selected_namespaces` payload
without leaking RAG concerns into the chat contract.
:::
#### Streaming and reasoning events
These `Display Events` provide real-time updates to user interfaces about an agent's internal processing.
| Event | Category | Purpose |
| :----------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------- |
| **`ChunkEvent`** | Display | Contains a small piece of a larger text response, enabling token-by-token streaming to the UI. |
| **`ThoughtEvent`** | Display | Provides a textual description of the agent's internal reasoning or current action, offering transparency into its process. |
##### **Observability and tracing events**
These events provide detailed telemetry for monitoring, debugging, and cost management. They are typically
`Display Events` but can sometimes also be `Control Events`.
| Event | Category | Purpose |
| :------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------ |
| **`LLMEvent`** | Control & Display | Records the details of a call to a Large Language Model, including the prompt, response, and invocation parameters. |
| **`RetrieverEvent`** | Control & Display | Records the results of a retrieval operation from a knowledge base, including the documents that were fetched. |
| **`LLMCostEvent`** | Display | Records the calculated cost of an LLM interaction, including token counts and associated expenses. |
#### Asynchronous interaction pattern events
These events manage complex, multi-step interactions that require pausing and resuming a workflow.
| Event | Category | Purpose |
| :-------------------------------- | :---------------- | :------------------------------------------------------------------------------------------------ |
| **`HumanInTheLoopRequestEvent`** | Control & Display | Pauses the workflow and sends a request to a human user for input or approval. |
| **`HumanInTheLoopResponseEvent`** | Control & Display | Carries the response from a human user, allowing the paused workflow to resume. |
| **`AgentInTheLoopRequestEvent`** | Control & Display | Pauses the workflow and delegates a task to another agent. |
| **`AgentInTheLoopResponseEvent`** | Control & Display | Carries the final `StopEvent` from the delegated agent, allowing the original workflow to resume. |
## Protocol in action: sequence diagrams
This section provides step-by-step walkthroughs of common interactions to illustrate how the protocol's participants and
events work together in practice. The following sequence diagrams visualize the flow of events between participants
during these interactions.
::: warning
`ThreadId`, `DisplayId`, `RunId` and `EventId` ObjectIds. However, for the purposes of this documentation, they are
represented as strings that start with prefix `t` for Thread, `d` for Display, `r` for Run, and `e` for Event.
:::
### Example: a simple user query
This scenario traces the lifecycle of a single user message, from the initial HTTP request to the final streamed
response. It demonstrates how a synchronous request is handled by the platform's asynchronous, event-driven core.
```mermaid
sequenceDiagram
participant User
participant Frontend
participant API Gateway
participant Event Bus
participant Agent
User->>+Frontend: Submits "Hello"
Frontend->>+API Gateway: POST .../stream with JSON payload
Note right of API Gateway: The API Gateway holds the HTTP connection open
to stream back the response.
API Gateway->>+Event Bus: Publishes UserMessageEvent (Control Event)
Event Bus->>+Agent: Delivers UserMessageEvent
Agent->>+Event Bus: Publishes ChunkEvent (Display Event)
Event Bus->>+API Gateway: Streams ChunkEvent back
API Gateway->>+Frontend: Streams SSE data
Frontend->>User: Renders "Hello! "
Agent->>+Event Bus: Publishes ChunkEvent (Display Event)
Event Bus->>+API Gateway: Streams ChunkEvent back
API Gateway->>+Frontend: Streams SSE data
Frontend->>User: Renders "How can I help?"
Agent->>+Event Bus: Publishes StopEvent (Control & Display Event)
Event Bus->>+API Gateway: Streams StopEvent back
API Gateway-->>-Frontend: Streams final SSE data and closes connection
Frontend-->>-User: Finalizes the response
```
#### Step 1: The user sends a message
The user types "Hello" into the chat interface. The Frontend packages this into an HTTP request to a dynamic endpoint on
the API Gateway. The `/stream` suffix indicates that the client expects a streaming response.
::: details Request / Response Details
**HTTP Request:** `POST /agents/MyChatAgent/dev_agent/UserMessageEvent/stream`
**Request Body:**
```json
{
"messages": [
{ "role": "user", "blocks": [{ "block_type": "text", "text": "Hello" }] }
]
}
```
:::
#### Step 2: The API Gateway initiates the workflow
The API Gateway authenticates the user, creates a new `Run` and `Display` context, and translates the HTTP request into
a `UserMessageEvent`. It then publishes this `Control Event` to the event bus on a precisely structured topic.
::: details NATs topic & Event Payload
**NATS Topic:** `agent.MyChatAgent.dev_agent.t948.d135.r4fg.control_event.UserMessageEvent.e423`
**Event Payload:**
```json
{
"event_id": "e423",
"created_at": 1755015355940833270,
"_event_name": "UserMessageEvent",
"_parent_event_names": ["UserMessageEvent", "StartEvent", "ControlAndDisplayEvent", "ControlEvent", "DisplayEvent"],
"locale": "en",
"user": { "id": "cc4af21b-981a-4a76-826d-e722715082e0", "name": "Test User" },
"messages": [
{ "role": "user", "blocks": [{ "block_type": "text", "text": "Hello" }] }
]
}
```
:::
An `Agent` instance subscribed to this topic consumes the event, which triggers the start of its workflow.
#### Step 3: The agent streams back the response
As the agent processes the request, it generates results and publishes `Display Events` as they become available. The
API Gateway receives these events from the bus and streams only their payload back to the Frontend as Server-Sent Events
(SSE).
::: details NATs topic & Event Payload (First Chunk)
**NATS Topic:**
`agent.MyChatAgent.dev_agent.t948.d135.r4fg.display_event.ChunkEvent.e453`
**SSE Stream to Frontend:**
```
data: {"event_id":"e453","created_at":1755015356940833271,"_event_name":"ChunkEvent","_parent_event_names":["ChunkEvent","DisplayEvent"],"display_name":{"en":"Chunk"},"display_description":{"en":"A chunk of a larger response."},"content":"Hello! "}\n\n
```
:::
::: details NATs topic & Event Payload (Second Chunk)
**NATS Topic:**
`agent.MyChatAgent.dev_agent.t948.d135.r4fg.display_event.ChunkEvent.e545`
**SSE Stream to Frontend:**
```
data: {"event_id":"e545","created_at":1755015357940833272,"_event_name":"ChunkEvent","_parent_event_names":["ChunkEvent","DisplayEvent"],"display_name":{"en":"Chunk"},"display_description":{"en":"A chunk of a larger response."},"content":"How can I help?"}\n\n
```
:::
#### Step 4: The agent completes the run
Once the agent has finished generating its response, it publishes a final `StopEvent`.
::: details NATs topic & Event Payload
**NATS Topic:**
`agent.MyChatAgent.dev_agent.t948.d135.r4fg.display_event.StopEvent.e598`
**SSE Stream to Frontend:**
```
data: {"event_id":"e598","created_at":1755015358940833273,"_event_name":"StopEvent","_parent_event_names":["StopEvent","ControlAndDisplayEvent","ControlEvent","DisplayEvent"],"display_name":{"en":"Stop"},"display_description":{"en":"Signals the end of a run."}}\n\n
```
:::
Upon streaming this final event, the API Gateway closes the HTTP connection. The Frontend finalizes the display, and the
interaction is complete.
### Example: an agent with an internal workflow
This scenario demonstrates how an agent can execute a multi-step internal process without exposing its intermediate
steps to the end-user. The user sends a single request and receives a single, final response. This is achieved by using
`Control Events` for internal state transitions and a final `Display Event` for the result.
```mermaid
sequenceDiagram
participant User
participant API Gateway
participant Event Bus
participant Agent
User->>+API Gateway: POST .../UserMessageEvent with JSON payload
API Gateway->>+Event Bus: Publishes UserMessageEvent (Control Event)
Event Bus->>+Agent: Delivers UserMessageEvent
Note over Agent, Event Bus: The agent now begins its internal workflow.
These events are for control flow only and are not displayed to the user.
Agent->>+Event Bus: Publishes CondenseQuestionEvent (Control Event)
Event Bus->>+Agent: Delivers CondenseQuestionEvent to next step
Agent->>+Event Bus: Publishes RetrieveEvent (Control Event)
Event Bus->>+Agent: Delivers RetrieveEvent to next step
Agent->>+Event Bus: Publishes StopEvent (Control & Display Event)
Event Bus->>+API Gateway: Delivers StopEvent
API Gateway-->>-User: Returns final HTTP 200 response with JSON payload
```
#### Step 1: The user sends a query
The user asks a question that requires the agent to retrieve information. The client makes a standard, non-streaming
HTTP request.
::: details Request / Response Details
**HTTP Request:**
```
POST /agents/RAGAgent/prod_rag/UserMessageEvent
```
**Request Body:**
```json
{
"messages": [
{
"role": "user",
"blocks": [{ "block_type": "text", "text": "What is the Swiss AI Hub?" }]
}
]
}
```
:::
#### Step 2: The API Gateway initiates the workflow
The Gateway creates the necessary contexts and publishes the `UserMessageEvent`. The API Gateway holds the HTTP
connection open, waiting for a final event to form the response.
::: details NATs topic & Event Payload
**NATS Topic:**
`agent.RAGAgent.prod_rag.t948.d135.r4fg.control_event.UserMessageEvent.e423`
**Event Payload:**
```json
{
"event_id": "e423",
"created_at": 1755015355940833270,
"_event_name": "UserMessageEvent",
"_parent_event_names": ["UserMessageEvent", "StartEvent", "ControlAndDisplayEvent", "ControlEvent", "DisplayEvent"],
"messages": [
{
"role": "user",
"blocks": [{ "block_type": "text", "text": "What is the Swiss AI Hub?" }]
}
]
}
```
:::
#### Step 3: The agent executes its internal workflow
The agent consumes the `UserMessageEvent` and begins a sequence of internal steps. Each step communicates with the next
by publishing a `Control Event`.
::: details NATs topic & Event Payload (First internal step)
**Condense Question:** **NATS Topic:**
`agent.RAGAgent.prod_rag.t948.d135.r4fg.control_event.CondenseQuestionEvent.e453`
**Event Payload:**
```json
{
"event_id": "e453",
"created_at": 1755015356940833271,
"_event_name": "CondenseQuestionEvent",
"_parent_event_names": ["CondenseQuestionEvent", "ControlEvent"],
"condensed_question": "Definition and purpose of the Swiss AI Hub"
}
```
:::
::: details NATs topic & Event Payload (Second internal step)
**Retrieve Documents:** **NATS Topic:**
`agent.RAGAgent.prod_rag.t948.d135.r4fg.control_event.RetrieveEvent.e545`
**Event Payload:**
```json
{
"event_id": "e545",
"created_at": 1755015357940833272,
"_event_name": "RetrieveEvent",
"_parent_event_names": ["RetrieveEvent", "ControlEvent"],
"nodes": [ { "id": "doc-1", "content": "The Swiss AI Hub is an open..." } ]
}
```
:::
Because these are **only `Control Events`** and not `Display Events`, they are not streamed to the API Gateway or any
observing client. They exist only on the internal event bus to orchestrate the agent's logic.
#### Step 4: The agent returns the final result
After the final internal step, the agent generates a complete answer and publishes it within a `StopEvent`. This event
is both a `Control Event` (terminating the run) and a `Display Event`.
::: details NATs topic & Event Payload
**NATS Topic:**
`agent.RAGAgent.prod_rag.t948.d135.r4fg.display_event.StopEvent.e598`
**Event Payload:**
```json
{
"event_id": "e598",
"created_at": 1755015358940833273,
"_event_name": "StopEvent",
"_parent_event_names": ["StopEvent", "ControlAndDisplayEvent", "ControlEvent", "DisplayEvent"],
"content": "The Swiss AI Hub is an open AI platform that you own and control."
}
```
:::
The API Gateway receives this single `Display Event`, uses its payload to construct the final HTTP response, and sends
it back to the client, closing the connection.
::: details HTTP Response Body
**HTTP Response Body:**
```json
{
"event_id": "e598",
"created_at": 1755015358940833273,
"_event_name": "StopEvent",
"_parent_event_names": ["StopEvent", "ControlAndDisplayEvent", "ControlEvent", "DisplayEvent"],
"content": "The Swiss AI Hub is an open AI platform that you own and control."
}
```
:::
## Agent2Agent Protocol
To better understand the design choices behind the Swiss AI Agent Protocol, it is useful to compare it with other
standards in the agentic ecosystem. The Agent2Agent (A2A) Protocol, an open standard for communication between
independent AI agents, serves as an excellent point of reference. While both protocols are event-driven and designed for
asynchronous operations, they solve different problems and operate at different levels.
### Core philosophy and scope
The most fundamental difference lies in their intended scope.
- **The A2A Protocol** is designed for **external interoperability**. Its primary goal is to enable agents built by
different vendors, on different platforms, to communicate with each other over the public internet or within a
corporate network. It treats each agent as an opaque, independent service.
- **The Swiss AI Agent Protocol** is designed for **internal cohesion**. It is the private, internal language that
orchestrates all components *within* a single, cohesive Swiss AI Hub instance. Its primary goals are extreme
decoupling of internal components, deep observability, and the management of long-running, autonomous processes within
the platform's secure boundary.
### Architectural model
The two protocols are based on different architectural patterns.
- **A2A** uses a **client-server model** over standard web transports (HTTP with JSON-RPC, gRPC, or REST). An A2A Client
makes a direct request to an A2A Server, which then manages a specific `Task`. This is a point-to-point interaction
model.
- **The Swiss AI Agent Protocol** uses a **publish-subscribe model** over a central message bus (NATS). Participants
publish events to the bus without knowledge of the subscribers. Any number of other participants - be it other agents,
the API Gateway, or logging services - can subscribe to these events. This is a broadcast-based, many-to-many
interaction model.
### State management
Their approaches to managing the state of an operation differ significantly.
- **In A2A**, the server-side agent is **stateful**. It creates and manages a `Task` object which progresses through a
defined lifecycle (`submitted`, `working`, `completed`, etc.). The state of the interaction is held by the remote
agent.
- **In the Swiss AI Agent Protocol**, the agent's code is **stateless**. The state of a `Run` is externalized and
managed by the platform's infrastructure. The event history is stored immutably in the message bus's stream
(JetStream), and ephemeral context is held in a distributed store (Redis). This allows any available agent instance to
process any event in a workflow, enabling high scalability and resilience.
### Data model and granularity
The structure of the communication itself reflects their different goals.
- **A2A** defines a set of RPC methods (`message/send`, `tasks/get`) and data objects (`Task`, `Message`, `Part`,
`Artifact`). This structure is well-suited for a remote procedure call system where a client manages a task on a
server.
- **The Swiss AI Agent Protocol** is more granular, centered on the strict distinction between `Control Events` and
`Display Events`. This high-resolution event stream is designed for maximum internal observability. Every internal
step, thought, and state transition can be an individual event, providing a complete audit trail of the agent's
execution.
### Discovery mechanism
How participants learn about each other is another key difference.
- **A2A** relies on a static **`AgentCard`**. This is a JSON document, often hosted at a well-known URI, that acts as a
digital business card, describing the agent's capabilities, endpoint, and authentication requirements.
- **The Swiss AI Agent Protocol** uses **dynamic, real-time discovery**. The API Gateway periodically broadcasts a
discovery request on the internal message bus. All running agents respond, allowing the Gateway to dynamically
generate and register its own secure, type-safe REST endpoints on the fly.
### Summary of differences
| Aspect | Swiss AI Agent Protocol | A2A Protocol |
| :--------------- | :---------------------------------------------- | :--------------------------------------------------- |
| **Primary Goal** | Internal cohesion, observability, and control | External interoperability between independent agents |
| **Architecture** | Publish-Subscribe (via Message Bus) | Client-Server (via HTTP/gRPC/REST) |
| **State** | Agent code is stateless; state is externalized | Remote agent is stateful; manages a `Task` object |
| **Data Unit** | `Event` (Control vs. Display) | `Task`, `Message`, `Part`, `Artifact` |
| **Granularity** | Very high; every internal step can be an event | Higher-level; focused on task state and results |
| **Discovery** | Dynamic; API endpoints are generated at runtime | Static; based on a published `AgentCard` |
### Interoperability and coexistence
The Swiss AI Agent Protocol and the A2A Protocol are not mutually exclusive; they are complementary and can work
together. The Swiss AI Agent Protocol governs the internal workings of the platform, while A2A can be used for
communication with the outside world.
A Swiss AI Hub instance could expose an **A2A Adapter Agent**. This specialized agent would act as a bridge:
1. **Externally,** it would present a standard A2A endpoint and an `AgentCard`, appearing as a compliant A2A agent to
the outside world.
2. **Internally,** it would be a participant in the Swiss AI Agent Protocol.
When this adapter agent receives an A2A `message/send` request, it would translate that request into an internal
`StartEvent` and publish it to the bus to trigger another, internal agent. It would then subscribe to the resulting
stream of internal `Display Events` and `StopEvent`s, translating them back into A2A `Task` updates and `Artifacts` to
send back to the external A2A client.
This approach allows the Swiss AI Hub to benefit from its highly observable and scalable internal architecture while
still participating openly in a broader, interoperable ecosystem of AI agents.
## Model Context Protocol (MCP)
The Model Context Protocol (MCP) is an open-source standard for connecting AI applications to external systems, such as
data sources, tools, and workflows. While both the Swiss AI Agent Protocol and MCP facilitate communication in AI
systems, they are designed to solve different problems and operate at different architectural layers. They are
complementary, not competing.
### Core philosophy and scope
The primary difference is their intended scope and purpose.
- **MCP** is designed for **connecting an agent to its tools**. It standardizes how an AI application (an MCP Host)
discovers and interacts with external capabilities (exposed by MCP Servers). Its focus is on providing context and
enabling action-taking in the outside world.
- **The Swiss AI Agent Protocol** is designed for **orchestrating the internal components of the platform**. It is the
private language that governs how autonomous agents, APIs, and other services within a Swiss AI Hub instance
collaborate. Its focus is on the lifecycle, state management, and observability of internal processes.
### Architectural model
The protocols are based on different interaction patterns.
- **MCP** uses a **client-server model**. An MCP Host (the AI application) creates a dedicated MCP Client for each MCP
Server it needs to communicate with. The interaction is a direct, point-to-point connection where the client requests
capabilities from the server.
- **The Swiss AI Agent Protocol** uses a **publish-subscribe model** over a central message bus. Participants publish
events without knowledge of who is listening. This enables a decoupled, many-to-many communication pattern where
multiple components can react to a single event.
### Data model and primitives
Their data models are tailored to their respective functions.
- **MCP** defines a set of primitives that a server can expose: `Tools` (executable functions), `Resources` (read-only
data), and `Prompts` (reusable templates). The protocol is centered on the AI application discovering and then
utilizing these capabilities.
- **The Swiss AI Agent Protocol**'s core primitive is the `Event`, strictly categorized into `Control Events` (which
drive logic) and `Display Events` (which provide observability). The protocol is centered on the flow of these events
to manage state transitions and report on activity within a distributed system.
### Summary of differences
| Aspect | Swiss AI Agent Protocol | Model Context Protocol (MCP) |
| :----------------- | :------------------------------------------------------------ | :--------------------------------------------------------- |
| **Primary Goal** | Internal orchestration and observability | Connecting an agent to external tools and data |
| **Architecture** | Publish-Subscribe (via Message Bus) | Client-Server (direct connections) |
| **Interaction** | Many-to-many, broadcast-based | One-to-one, request-response |
| **Core Primitive** | The `Event` (Control vs. Display) | `Tool`, `Resource`, `Prompt` |
| **Purpose** | To manage the internal state and flow of autonomous processes | To provide an AI application with capabilities and context |
### Interoperability and coexistence
The two protocols can coexist and complement each other effectively. A Swiss AI Hub agent can act as an MCP Host to
interact with external tools.
This is achieved by creating an **MCP Adapter** within the agent's workflow. The adapter is a component that translates
between the two protocols.
```mermaid
sequenceDiagram
participant InternalAgent as Agent (SAAP)
participant EventBus as Event Bus (SAAP)
participant McpAdapter as MCP Adapter
participant McpServer as External Tool (MCP Server)
InternalAgent->>+EventBus: Publishes ExecuteMcpToolEvent (Control Event)
EventBus->>+McpAdapter: Delivers event
Note over McpAdapter, McpServer: The adapter now acts as an MCP Client.
McpAdapter->>+McpServer: tools/call (MCP Request)
McpServer-->>-McpAdapter: Returns tool result (MCP Response)
McpAdapter->>+EventBus: Publishes McpToolResultEvent (Control Event)
EventBus->>+InternalAgent: Delivers result
```
In this flow:
1. An internal agent, operating on the Swiss AI Agent Protocol, decides it needs to use an external tool. It publishes a
`Control Event` (e.g., `ExecuteMcpToolEvent`) containing the tool name and arguments.
2. The `McpAdapter`, subscribed to such events, consumes it.
3. The adapter then acts as an MCP Client, sending a standard `tools/call` JSON-RPC request to the external MCP Server.
4. When the MCP Server responds, the adapter packages the result into a new internal `Control Event` (e.g.,
`McpToolResultEvent`) and publishes it back to the event bus.
5. The original agent, or another subscribed agent, consumes the result and continues its workflow.
This pattern allows agents within the Swiss AI Hub to leverage the rich ecosystem of external tools and data sources
available via MCP, while still benefiting from the robust internal orchestration, security, and observability provided
by the Swiss AI Agent Protocol.
## Agent User Interaction Protocol (AG-UI)
The Agent User Interaction Protocol (AG-UI) is an open standard designed specifically to standardize communication
between front-end applications and AI agents. It focuses on the agent-to-user interactivity layer. Like the other
protocols discussed, AG-UI is complementary to the Swiss AI Agent Protocol, addressing a different part of the overall
system architecture.
### Core philosophy and scope
The protocols are designed with different scopes and objectives in mind.
- **AG-UI** is a **presentation-layer protocol**. Its exclusive focus is to create a standardized, real-time
communication channel between an AI agent and a client application (the user interface). It defines a vocabulary for
streaming UI updates, managing shared state for the UI, and handling human-in-the-loop interactions.
- **The Swiss AI Agent Protocol** is a **full-stack, internal orchestration protocol**. It governs the communication
between *all* internal components of the platform, not just the link to the UI. Its scope includes agent-to-agent
delegation, process orchestration, and the management of long-running, autonomous tasks that may have no UI at all.
### Architectural model
Their architectural models reflect their different scopes.
- **AG-UI** defines a **client-server model for the UI channel**. An AG-UI Client (in the frontend) connects to an AG-UI
compatible agent or server. The protocol standardizes the events that flow over this specific connection.
- **The Swiss AI Agent Protocol** uses a **platform-wide publish-subscribe model**. Events are broadcast on a central
message bus and can be consumed by any authorized participant. The API Gateway acts as a bridge to the frontend, but
the protocol itself governs the entire internal ecosystem.
### Tool and state management
The two protocols have fundamentally different philosophies regarding tools and state.
- **In AG-UI**, tools are **frontend-defined**. The client application declares which tools are available and passes
them to the agent during a run. The agent can then request to call these tools, but the implementation and execution
happen on the client side. State management is also UI-centric, with `STATE_SNAPSHOT` and `STATE_DELTA` events
designed specifically to keep a client's UI in sync with the agent.
- **In the Swiss AI Agent Protocol**, an agent's capabilities are **backend-defined** and inherent to its
implementation. The agent's tools and logic are part of its own secure backend service. State management is more
general-purpose, with `RunContext` and `ThreadContext` designed to manage the persistent state of long-running backend
processes, not just UI synchronization.
### Event model
While both are event-driven, their event vocabularies are tailored for different purposes.
- **AG-UI** specifies a concise set of approximately 16 event types (e.g., `TEXT_MESSAGE_CHUNK`, `TOOL_CALL_START`,
`STATE_DELTA`) that are directly mapped to common UI rendering and interactivity needs.
- **The Swiss AI Agent Protocol** has an extensive and extensible library of events. Its key distinction is the strict
separation of `Control Events` from `Display Events`, which allows for complex internal workflows that are decoupled
from what is shown to the user.
### Summary of differences
| Aspect | Swiss AI Agent Protocol | Agent User Interaction Protocol (AG-UI) |
| :--------------- | :------------------------------------------------------------ | :------------------------------------------------ |
| **Primary Goal** | Full-stack internal orchestration and control | Standardizing the agent-to-UI communication link |
| **Scope** | Entire internal platform | The presentation layer |
| **Architecture** | Publish-Subscribe (many-to-many) | Client-Server (for the UI channel) |
| **Tools** | Backend-defined, inherent to the agent | Frontend-defined, passed to the agent |
| **State** | Manages backend process state (`Run`/`Thread` Context) | Synchronizes UI state (`Snapshot`/`Delta` events) |
| **Event Model** | Extensible library; strict `Control` vs. `Display` separation | Fixed set of UI-centric events |
### Interoperability and coexistence
The two protocols are highly complementary. The Swiss AI Agent Protocol can serve as the backend engine for an AG-UI
compatible server, allowing frontends built with tools like CopilotKit to connect seamlessly to a Swiss AI Hub instance.
This is achieved by creating an **AG-UI Adapter**. This adapter is a service that translates between the two protocols.
```mermaid
sequenceDiagram
participant Frontend as Frontend (AG-UI Client)
participant AGUIAdapter as AG-UI Adapter
participant EventBus as Event Bus (SAAP)
participant InternalAgent as Agent (SAAP)
Frontend->>+AGUIAdapter: POST with AG-UI formatted request
AGUIAdapter->>+EventBus: Publishes UserMessageEvent (SAAP Control Event)
EventBus->>+InternalAgent: Delivers event
InternalAgent->>+EventBus: Publishes ChunkEvent (SAAP Display Event)
EventBus->>+AGUIAdapter: Consumes SAAP Display Event
AGUIAdapter->>+Frontend: Streams TEXT_MESSAGE_CONTENT (AG-UI Event)
InternalAgent->>+EventBus: Publishes StopEvent (SAAP Control & Display Event)
EventBus->>+AGUIAdapter: Consumes SAAP StopEvent
AGUIAdapter-->>-Frontend: Streams RUN_FINISHED (AG-UI Event) & closes connection
```
In this model, the `AG-UI Adapter`:
1. Exposes an HTTP endpoint that is compliant with the AG-UI specification.
2. Receives AG-UI requests from the frontend and translates them into internal Swiss AI Agent Protocol `Control Events`.
3. Subscribes to the internal `Display Event` stream for a given `Display` context.
4. Translates the granular internal events (like `ChunkEvent` and `ThoughtEvent`) into the corresponding AG-UI events
and streams them back to the frontend.
This allows developers to build rich user interfaces using AG-UI-native tools while leveraging the security,
scalability, and observability of the Swiss AI Hub's backend protocol.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/2_architecture/4_network_isolation.md
title: "Docker Network Isolation"
description: ""
---
# Docker Network Isolation
The Swiss AI Hub platform implements network segmentation to enforce security boundaries between services. This
defense-in-depth approach limits the blast radius of potential security breaches and enforces the principle of least
privilege at the network layer.
## Network Zones
The platform uses six isolated Docker networks:
| Network | Purpose | External Access | ICC Enabled |
| -------------- | ------------------------------------ | ---------------- | ----------- |
| `proxy` | External traffic via Traefik | Ingress + Egress | Yes |
| `backend` | Internal application services | No | Yes |
| `data` | Databases and message broker | No | Yes |
| `storage` | SeaweedFS object storage | No | Yes |
| `egress` | Outbound internet access only | Egress only | No |
| `code-sandbox` | Code-execution sandbox + its callers | No | Yes |
The `egress` network is designed for services that need to reach the internet (outbound) but should not be reachable
from the internet (no ingress). Inter-Container Communication (ICC) is disabled on this network, meaning containers
cannot communicate with each other via this network—they can only use it for outbound internet access.
The `code-sandbox` network is a single-tenant zone for the `open-terminal` code-execution sandbox, which runs arbitrary
user-submitted code. Its membership is the sandbox plus **exactly its callers** (`open-webui`, `universal-agent`, the
one AI-Hub agent that runs code, and `api`, which serves each user's own files). Because Docker networks are bidirectional, keeping the sandbox off `backend` is what
prevents a sandbox breakout from laterally reaching `litellm`, `vLLM`, `mineru`, `presidio`, `speaches`, or
`otel-collector`. ICC stays enabled (the callers must reach `open-terminal:8000`), and `internal: true` in non-dev
stages also denies the sandbox any outbound internet.
## Service Network Assignments
Each service is assigned to only the networks it requires for operation:
### Proxy Network Services
Services accessible from outside the Docker network:
- **traefik**: Reverse proxy and API gateway
- **api**: REST API and WebSocket endpoints
- **web**: Admin UI frontend
- **open-webui**: Chat interface
- **bot**: MS Teams/Slack integration
- **seaweedfs-s3**: S3-compatible storage API
- **oauth2proxy-**\*: Authentication proxies
### Backend Network Services
Internal application and processing services:
- **litellm**: LLM gateway and request routing
- **mineru-api**: Document parsing and extraction
- **presidio-analyzer/anonymizer**: PII detection and anonymization
- **vLLM**: Local LLM inference (chat, embedding, reranking) — GPU deployments only
- **speaches**: Speech-to-text and text-to-speech
- **playwright**: Web scraping and automation (also on `egress` for internet access)
- **agents**: All agent workers (rag, expert, wrapping)
- **pipelines**: Data processing pipelines
- **dagster-**\*: Pipeline orchestration
- **langfuse**: AI observability
- **otel-collector**: Telemetry aggregation
### Data Network Services
Persistence and messaging infrastructure:
- **postgres**: Primary PostgreSQL database
- **pgbouncer**: Connection pooler for Dagster
- **postgres-ferretdb**: PostgreSQL backend for FerretDB
- **ferretdb**: MongoDB-compatible document store
- **neo4j**: Graph database for Mem0 memory storage
- **milvus-standalone**: Vector database
- **etcd**: Distributed key-value store (Milvus metadata)
- **valkey**: Redis-compatible in-memory cache
- **nats**: Message broker for event-driven communication
### Storage Network Services
Distributed object storage cluster:
- **seaweedfs-master**: Cluster coordinator
- **seaweedfs-volume**: Data storage nodes
- **seaweedfs-filer**: File system interface
- **seaweedfs-s3**: S3 API gateway
- **etcd**: Filer metadata backend
### Egress Network Services
Services that require outbound internet access but no inbound access:
- **playwright**: Web scraping and browser automation (needs to fetch web pages)
This network has ICC (Inter-Container Communication) disabled, preventing lateral movement between containers on this
network. Services use `egress` solely for outbound internet access and must use other networks (e.g., `backend`) for
inter-service communication.
### Code-Sandbox Network Services
The single-tenant zone for the code-execution sandbox:
- **open-terminal**: Code execution sandbox for OpenWebUI (plain LLM models; per-user home isolation — see ADR
`docs/arc42/decisions/2026_06_22_openwebui_code_execution_open_terminal.md`)
The sandbox is the **only** resident; its callers join this network in addition to their own. `open-webui` is attached
to `code-sandbox` (alongside `proxy`/`backend`/`data`/`storage`) so it can reach `open-terminal:8000`, and
`universal-agent` joins it (alongside `backend`/`data`/`storage`) to run code in the asking user's sandbox home, and `api` joins it to serve each user's own files from there. The
`sandbox-mirror` sidecar that copies the homes to S3 reads the homes volume and sits on `backend` only, never on
`code-sandbox`. Because
the sandbox shares a network only with its callers, a breakout has no network path to the `backend` or `data` tiers. In
dev, `open-webui` uses `network_mode: host` and the agents run locally outside Docker, so both reach the sandbox via the
published `localhost:8200` port instead.
## Network Topology
```mermaid
flowchart TB
subgraph Internet
ext[External Traffic]
websites[External Websites]
end
subgraph proxy[PROXY NETWORK]
traefik[traefik]
api[api]
web[web]
openwebui[open-webui]
bot[bot]
s3proxy[seaweedfs-s3]
end
subgraph backend[BACKEND NETWORK]
litellm[litellm]
mineru-api[mineru-api]
presidio[presidio]
vllm[vLLM]
agents[agents]
playwright[playwright]
dagster[dagster-*]
pipelines[pipelines]
langfuse[langfuse]
otel[otel-collector]
end
subgraph egress[EGRESS NETWORK - ICC Disabled]
playwright_egress[playwright]
end
subgraph code-sandbox[CODE-SANDBOX NETWORK - sandbox + callers only]
open-terminal[open-terminal]
openwebui_cs[open-webui]
end
subgraph data[DATA NETWORK]
postgres[postgres]
ferretdb[ferretdb]
neo4j[neo4j]
milvus[milvus]
valkey[valkey]
nats[nats]
etcd[etcd]
end
subgraph storage[STORAGE NETWORK]
swmaster[seaweed-master]
swvolume[seaweed-volume]
swfiler[seaweed-filer]
end
ext -->|:80, :443| traefik
traefik --> api
traefik --> web
traefik --> openwebui
traefik --> bot
traefik --> s3proxy
api --> litellm
api --> agents
openwebui --> litellm
openwebui_cs --> open-terminal
openwebui --> playwright
agents --> nats
agents --> milvus
agents --> neo4j
pipelines --> nats
dagster --> postgres
langfuse --> postgres
litellm --> postgres
milvus --> etcd
milvus --> s3proxy
swfiler --> etcd
s3proxy --> swfiler
swfiler --> swvolume
swvolume --> swmaster
playwright_egress -->|outbound only| websites
```
## Security Implications
### What Each Network Boundary Protects
**Proxy → Backend Boundary**
- External users cannot directly access internal processing services
- Compromised Traefik cannot reach databases without going through API
**Backend → Data Boundary**
- Processing services access databases through defined interfaces
- Compromised AI service cannot directly manipulate other databases
**Data → Storage Boundary**
- SeaweedFS internal cluster communication is isolated
- Database services cannot interfere with storage operations
### Service Visibility Matrix
| From \\ To | proxy | backend | data | storage | egress | code-sandbox | Internet |
| ------------ | ----- | ------- | ---- | ------- | ------ | ------------ | -------- |
| External | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | - |
| proxy | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ |
| backend | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| data | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ |
| storage | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| egress | ✗ | ✗ | ✗ | ✗ | ✗\* | ✗ | ✓ |
| code-sandbox | ✗ | ✗ | ✗ | ✗ | ✗ | ✓\*\* | ✗\*\*\* |
\*ICC disabled on egress network - containers cannot communicate with each other via this network.
\*\*Only `open-terminal` and its callers (`open-webui`, `universal-agent`, `api`) reside on `code-sandbox`, so the sandbox can
reach its callers but no other tier.
\*\*\*`internal: true` in non-dev stages blocks outbound internet from the sandbox; in `dev` the network is non-internal
(localhost access), so this guarantee applies to `local`/`build`/`nightly`/`latest` only.
## Operational Considerations
### Adding New Services
When adding a new service, determine which networks it needs:
1. **Needs external access (ingress)?** → Add to `proxy`
2. **Is an application service?** → Add to `backend`
3. **Needs database access?** → Add to `data`
4. **Needs object storage?** → Add to `storage`
5. **Needs outbound internet only (no ingress)?** → Add to `egress`
Note: The `egress` network is specifically for services that need to reach external websites/APIs but should not be
reachable from outside. It has ICC disabled, so services on `egress` cannot communicate with each other—use `backend`
for inter-service communication.
### Debugging Network Issues
If a service cannot reach another service:
1. Verify both services are on a common network
2. Check if the target network is marked `internal: true`
3. Use `docker network inspect ` to see connected containers
4. Check service names match DNS expectations (container_name)
### Network Inspection Commands
```bash
# List all networks
docker network ls
# Inspect a specific network
docker network inspect backend
# See which networks a container is connected to
docker inspect --format '{{json .NetworkSettings.Networks}}'
```
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/2_architecture/5_package_views.md
title: "Package-Centered Views"
description: "Developer-onboarding architecture views — one per first-party package, with plain-language explanation of what it does and the L2 surface it touches."
---
# Package-Centered Views
This page is the starting point for a developer about to work **inside** a Swiss AI Hub package. For each first-party
package it pairs two things:
1. **Plain-language prose** — what the package does, why it exists, the problem it solves, and what it contains.
2. **A centered architecture diagram** — the package in the middle, with every L2 container and external system it talks
to (inbound *and* outbound). It answers: *"If I touch this package, what else will I be interacting with?"*
These views are deliberately separate from the [Code Deep Dive](../../../6_code_deep_dive/) section, which mirrors each
package's `README.md` — the end-user document that also appears on PyPI and npm. That audience installs the package;
this audience builds it. For the layered platform story (what lives in the Data tier, the LLM tier, and so on) see
[Containers](../2_containers/).
## API Gateway
**`packages/api`** is the bridge between the outside world and the platform. Everything a client does — a browser
loading the Admin UI, OpenWebUI streaming a chat, a script calling REST — enters the platform through here. Inside the
platform, agents, processes, and pipelines speak to each other over NATS using the Swiss AI Agent Protocol; the API
translates that event-driven world into the request/response and WebSocket idioms that HTTP clients expect.
**Why it exists.** The platform's internal communication is asynchronous and event-sourced, which is powerful for agents
but awkward for a frontend. The API absorbs that mismatch: it publishes Control Events, subscribes to the resulting
Display Events, and streams them back over a WebSocket so the UI can render an agent's reasoning live. It also owns the
relational concerns that don't belong in any single agent — tenants, users, roles, knowledge-base metadata.
**What it contains.** A set of composable FastAPI controllers following a strict Controller → Service → Entity layering,
plus an embedded **Process Engine** (`packages/process` runs in-process here, not as its own container), a dynamic
endpoint-discovery mechanism that surfaces online agents as REST routes, and the WebSocket display-event sender.
Because the API is the most-connected first-party container, its view is split into outbound and inbound for legibility.
### Outbound — what the API calls
### Inbound — who calls the API
## Sysadmin API
**`packages/sysadmin-api`** is the system-administration plane — the endpoints that operate *above* any single tenant:
creating and deleting tenants, assigning platform-level roles, and other `AIHubSysAdmin`-gated operations. It runs as
its own FastAPI service on `sysadmin.${DOMAIN}/api/v1/*`.
**Why it's a separate package.** The main API ships under Apache-2.0; the sysadmin plane ships under AGPL-3.0-or-later
(network-copyleft). Keeping it a physically separate, separately-licensed artifact prevents AGPL terms from leaking onto
the Apache-2.0 code. It also enforces a hard security boundary — every endpoint requires the `AIHubSysAdmin` realm role.
**What it contains.** Its own `TenantAdminController` (tenant lifecycle), plus a curated set of controllers *re-mounted*
from `packages/api` (user, role, account, auth-provider) so the Sysadmin UI's inherited composables resolve same-origin.
Code ownership stays in `packages/api`; this package only picks the surface it needs and gates it.
## Admin UI
**`packages/web`** is the admin and management interface — where administrators configure agents, manage knowledge
bases, monitor processes, inspect conversation threads, assign roles, and track costs. It is a Nuxt 3 SPA published as a
reusable Nuxt layer.
**Why it exists.** The platform exposes a large, evolving surface; OpenWebUI covers end-user chat, but configuring and
operating the platform needs a purpose-built management console. The UI consumes the API entirely through a generated
TypeScript SDK, so backend contract changes surface as type errors at build time rather than runtime surprises.
**What it contains.** Domain-scoped pages (agents, processes, threads, knowledge, models, roles, users, costs,
evaluations), Pinia-Colada query/mutation composables, a WebSocket bridge that streams agent display events straight
into the cache, an event-display component system that renders each agent event type, and a PrimeVue + Tailwind design
system.
## Sysadmin UI
**`packages/sysadmin-web`** is the system-administration console — today, the multi-tenant management UI. It is a Nuxt 3
Layer that *extends* `@swiss-ai-hub/web`, reusing its components, composables, and design system while adding
sysadmin-only pages. It is hosted at `sysadmin.${DOMAIN}/*`.
**Why it's a separate package.** Same reasoning as the Sysadmin API: it is a separate deploy artifact with its own
security boundary on the `sysadmin.${DOMAIN}` subdomain. Both `packages/web` and the sysadmin plane ship under
AGPL-3.0-or-later, so the separation is architectural rather than a licensing boundary. The Nuxt Layer mechanism lets it
inherit nearly everything from the open-source UI without copying code.
**What it contains.** Tenant CRUD pages and the sysadmin route guard, layered on top of the inherited Admin UI. Its SDK
and most API calls resolve same-origin against the Sysadmin API; the only cross-origin call is the role check that
bounces a non-sysadmin user back to the main app.
## Agent Runtime
**`packages/agent`** is the SDK for building agents — the transparent, workflow-based AI workers that are the reason the
platform exists. An agent is a small, stateless Python class: you declare a few `@step` methods and the events that
trigger them, and the runtime handles the rest.
**Why it exists.** Most agent frameworks are opaque — a prompt goes in, an answer comes out, and the reasoning is a
black box. Swiss AI Hub agents are auditable by construction: every step is an event on NATS JetStream, so a run can be
replayed, traced, and inspected. The runtime makes agents horizontally scalable and independently deployable — each
agent class runs as its own container, subscribing to its own subjects.
**What it contains.** The dispatcher that replays event history to decide which steps to run, the `@step` decorator and
step registry, a dependency injector that resolves step parameters by type, the precondition evaluator, and the durable
`RunContext` / `ThreadContext` state stores (in Valkey). At runtime an agent reaches RAG (Milvus), memory (Neo4j), the
LLM gateway (LiteLLM), and any external MCP tools it's configured with.
## Pipeline Orchestrator
**`packages/pipeline`** is the data-ingestion SDK — it turns an organization's documents into the RAG-ready vectors that
agents search. If the Agent Runtime is how the platform *answers* questions, the pipeline is how the knowledge to answer
them *gets in*.
**Why it exists.** Useful enterprise agents need organizational context, and that context lives in scattered, messy
document stores — SharePoint, OneDrive, S3, network drives. Getting it into a vector store is a multi-stage problem:
discover changes, download, parse (OCR, layout extraction), chunk semantically, embed, and index — with lineage from
every vector back to its source. The pipeline models this as a Dagster asset graph, so every stage is observable and
re-runnable.
**What it contains.** A two-stage factory pattern: source connectors (Rclone for 70+ backends, plus direct MS Graph for
SharePoint) feed a unified processing graph (MinerU for parsing, LiteLLM for embeddings, Milvus for storage). It emits
`SourceUpdatedEvent` to NATS on ingest and posts failure alerts to notification targets via Apprise.
## Bot Service
**`packages/bot`** brings agents to users where they already work — Microsoft Teams, Slack, and web chat — instead of
requiring them to visit a dedicated UI. It is built on the `microsoft-agents-*` SDK (the successor to the Microsoft Bot
Framework).
**Why it exists.** Adoption friction is real: a dedicated AI interface is one more tool to switch to. Meeting users
inside their existing collaboration platform removes that friction. The bot also supports *bot-in-the-loop* (BITL) — an
agent mid-workflow can ask the user a clarifying question directly in their Slack thread and resume when they answer.
**What it contains.** A `BaseChatBot` + `CompletionHandler` strategy pattern that keeps channel-specific logic (Slack,
Teams, Webex, email) separate from conversation logic, NATS bridges for agent chat and BITL, per-message identity
resolution against Keycloak, and a direct LiteLLM path for simple non-agent completions. Conversation state is held in
FerretDB with a TTL.
## Backup Service
**`packages/backup`** is the platform's backup, restore, and PostgreSQL-maintenance plane — a self-contained Dagster
instance (independent of the ingestion pipeline) that snapshots every stateful store to S3.
**Why it exists.** The platform spreads state across seven very different stores — PostgreSQL, FerretDB, Milvus, Neo4j,
ClickHouse, Valkey, and NATS JetStream — each with its own backup mechanism. A single, scheduled, auditable service that
knows how to snapshot and restore each one is far safer than ad-hoc per-store scripts. Running it as its own Dagster
instance keeps backup scheduling independent of pipeline workloads.
**What it contains.** Three parallel asset graphs (backup, restore, maintenance), one handler per stateful store, an S3
manager for writing artifacts to SeaweedFS, and container-discovery logic. It talks to the Docker socket *directly* (not
through the OIDC-tier socket proxy, which is Traefik-only) to quiesce and control containers during backup. It
deliberately excludes etcd, which is treated as ephemeral.
## OpenWebUI
**OpenWebUI** is the open-source chat interface — the primary end-user surface for conversational interaction with
agents. It is a third-party application we ship and integrate, not a package we author, but its integration is deep
enough to warrant its own onboarding view.
**Why it matters architecturally.** OpenWebUI is where most users actually talk to the platform, and its connection to
our API is more than a simple OpenAI-compatible endpoint. A **custom `aihub-pipeline`** (a Python function mounted into
OpenWebUI) bridges chat to the Swiss AI Agent Protocol over SSE — preserving rich agent events (reasoning, tool calls,
retrieval, human-in-the-loop) as structured UI blocks rather than flat token streams.
**What to know.** The custom pipeline path is distinct from the OpenAI-compatible fallback, which OpenWebUI uses for
non-agent tasks (image generation, speech, embeddings, document parsing — all proxied through our API). OpenWebUI also
calls Milvus directly for its own built-in RAG feature, stores state in PostgreSQL, files in SeaweedFS, and
authenticates via Keycloak. The API, in turn, provisions agent models and access into OpenWebUI via SCIM.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide.md
title: "Deployment Guide"
description: ""
---
# Deployment Guide
@mhoegger
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/1_deployment_options.md
title: "Deployment Options"
description: ""
---
# Deployment Options
## Overview
The Swiss AI Hub can be deployed as a single isolated instance for one organization, or as multiple isolated instances
that optionally share backend LLM resources.
::: info Multi-tenancy vs Multi-instancing
This chapter describes **multi-instancing** (multiple isolated Swiss AI Hub instances). For **multi-tenancy** (multiple
organizational boundaries within a single instance), see [Multi-tenancy](../../16_multi_tenancy/).
Both deployment models are valid and serve different purposes. Multi-instancing provides hard isolation between
organizations, while multi-tenancy provides logical separation within a shared platform instance.
:::
## Single instance deployment
### Isolated instance
A single instance deployment runs a complete, self-contained Swiss AI Hub instance. The organization gets dedicated
infrastructure: separate databases, vector stores, file storage, and application services.
The instance includes the API, agents, pipelines, web interface, and bot integrations. It has its own databases
(FerretDB/PostgreSQL), vector stores (Milvus), and file storage (SeaweedFS). Monitoring runs through Langfuse and
OpenTelemetry. NATS handles event streaming. The instance has its own LiteLLM proxy for cost tracking and version
control.
### LLM backend
The instance connects to LLM services via its LiteLLM proxy. Non-GPU deployments route through Swiss LLM Cloud (a
Swiss-hosted provider). GPU deployments run all inference locally via vLLM on an NVIDIA RTX 6000 Pro (96 GB VRAM). The
proxy handles model selection, budgets, rate limits, and versions. All prompts, responses, and user data stay within the
instance.
______________________________________________________________________
## Hosting options
The Swiss AI Hub can be hosted in three ways depending on organizational requirements.
### On-premise (bring your own server)
You run the Swiss AI Hub on your own servers in your data center.
You need x86_64 servers with CPU, RAM, and storage. NVIDIA GPUs work for self-hosted LLM inference. For network access,
either outbound HTTPS for cloud-based LLM services, or air-gapped with local models.
Infrastructure is under your control. No cloud dependencies. Works in air-gapped environments with self-hosted LLMs.
______________________________________________________________________
### Private cloud (bring your own cloud)
You run the Swiss AI Hub in your own cloud environment (Swiss cloud provider, Azure, AWS, GCP).
Data stays in your cloud account under your control. You choose the region (e.g., Switzerland for data residency). You
manage the cloud resources and costs.
Cloud providers typically have security and compliance certifications. You need internet connectivity for LLM proxy
access (HTTPS), optionally VPN for administrative access, and private networking between services (internal DNS).
______________________________________________________________________
### SaaS (Swiss cloud hosting)
bbv hosts and manages the Swiss AI Hub for you on Swiss-based cloud infrastructure.
bbv handles infrastructure provisioning, updates, backups, monitoring, and operational tasks. Data stays in Switzerland
under Swiss legal jurisdiction. Security and compliance certifications from the cloud provider.
You access the Swiss AI Hub through a web interface and APIs. bbv provides SLAs for uptime and support. Less operational
overhead for your team.
______________________________________________________________________
## Multi-instance deployment
::: tip When to use multi-instancing
Use multiple isolated instances when you need **hard separation** between organizations with 0% chance of data leakage.
For example, a health insurance company with a medical review commission that handles top-secret data requiring absolute
isolation from the main insurance department.
Even a misconfiguration of the Swiss AI Hub cannot cause data leakage between instances. Admins from one instance cannot
configure or access another instance without a separate login.
For logical separation within a shared platform, use [multi-tenancy](../../16_multi_tenancy/) instead.
:::
### Shared LLM backend
When deploying multiple instances, they can share backend LLM resources. Multiple instances can use the same Swiss LLM
Cloud credentials, or share a local vLLM GPU server. They can also share authentication infrastructure like Azure AD or
Keycloak.
Each instance still has their own LiteLLM proxy. The proxy handles model selection, budgets, rate limits, and versions
per instance. LLM usage is tracked per instance. Prompts, responses, and user data stay within each instance.
The shared LLM backends are stateless. They don't persist prompts or responses. Conversational context and history
remain in each instance's own infrastructure.
## Characteristics
### Data isolation
Each instance's data stays isolated. There's no shared database or vector store. Data can't leak between organizations.
The setup meets Swiss Data Protection Law (revDSG), GDPR data isolation requirements, and Swiss public sector security
standards.
::: info Multi-tenancy within instances
Each instance can also use [multi-tenancy](../../16_multi_tenancy/) to create logical boundaries for departments,
customers, or projects within that instance. Multi-tenancy provides flexible access control while maintaining hard
isolation between instances.
:::
### Configuration
Each instance can be configured independently. Organizations can deploy custom agents, specialized pipelines for their
data sources, their own access control (RBAC, OIDC with local IdP), custom knowledge bases, and dedicated authentication
providers like Azure AD or Keycloak.
### Scaling and updates
Resource allocation is per-instance. You scale compute, memory, and storage based on actual usage. Each instance can
apply updates on their own schedule. Testing new features in one instance doesn't affect others. SLAs vary per contract.
### Compliance and auditing
Auditors can inspect a single instance's infrastructure. Logs and traces stay within the instance. Backup retention
policies can be configured per instance. Penetration testing can be scoped to individual instances.
## Deployment model
### Single instance infrastructure
A single instance deployment contains:
```
Swiss AI Hub Instance
├── Application Layer
│ ├── API Service (FastAPI + WebSocket gateway)
│ ├── Web Interface (Nuxt.js frontend)
│ ├── OpenWebUI (LLM chat interface)
│ ├── Agent Services (RAG, specialized agents)
│ ├── Pipeline Services (Dagster + custom pipelines)
│ └── Bot Service (MS Teams, Slack integrations)
│
├── Data Layer
│ ├── Database (FerretDB + PostgreSQL)
│ ├── Vector Store (Milvus)
│ ├── Document Store (SeaweedFS)
│ └── Cache (Valkey)
│
├── LLM Layer
│ ├── LiteLLM Proxy
│ │ ├── Cost tracking and budgets
│ │ ├── Model routing configuration
│ │ ├── Rate limiting
│ │ └── Version control
│ └── Presidio (PII anonymization)
│
├── Observability Layer
│ ├── Langfuse (LLM tracing, cost tracking, and evaluation)
│ └── OpenTelemetry (distributed tracing)
│
└── Infrastructure Layer
├── NATS (message bus)
├── MinerU (document processing)
└── Traefik (reverse proxy + SSL termination)
```
The LiteLLM proxy connects to LLM services (Swiss LLM Cloud for non-GPU, local vLLM for GPU deployments).
### Multi-instance infrastructure
When deploying multiple instances, each instance gets the same infrastructure shown above. They can share backend LLM
resources:
```
Shared LLM Backend Resources
├── Cloud LLM Provider
│ ├── Swiss LLM Cloud credentials (shared API keys)
│ └── Other cloud provider credentials (optional)
│
├── Self-Hosted Model Infrastructure (GPU)
│ └── vLLM deployment (NVIDIA RTX 6000 Pro, 96 GB VRAM)
│
└── Optional Shared Services
├── Central Authentication (Azure AD, Keycloak)
└── Central Monitoring Dashboard (optional)
```
Network architecture:
- Each instance has their own LiteLLM proxy
- Instance LiteLLM proxies connect to shared LLM backends (Swiss LLM Cloud or local vLLM)
- Shared LLM backends use common API credentials (configured per instance's LiteLLM)
- No direct communication between instances
- Optional: Shared authentication provider (Azure AD, Keycloak)
Data isolation and sovereignty. Independent scaling and resource allocation. Custom configurations per instance.
Flexible update schedules. Clear compliance boundaries.
______________________________________________________________________
## Architecture diagrams
### Single instance deployment
```mermaid
graph TB
subgraph Instance["Swiss AI Hub Instance"]
Stack["Full Stack
(API, Agents, DB, Vector Store)"]
Proxy["LiteLLM Proxy"]
Stack --- Proxy
end
Backend["LLM Backend
(Swiss LLM Cloud or local vLLM)"]
Proxy -->|HTTPS| Backend
classDef default font-size:16px,padding:20px
```
The instance connects to LLM services via its LiteLLM proxy.
### Multi-instance deployment with shared LLM backend
```mermaid
graph TB
Backend["Shared LLM Backend
(Swiss LLM Cloud or local vLLM)"]
subgraph Instance1["Instance 1"]
I1Stack["Full Stack
(API, Agents, DB, Vector Store)"]
I1Proxy["LiteLLM Proxy"]
I1Stack --- I1Proxy
end
subgraph Instance2["Instance 2"]
I2Stack["Full Stack
(API, Agents, DB, Vector Store)"]
I2Proxy["LiteLLM Proxy"]
I2Stack --- I2Proxy
end
subgraph Instance3["Instance 3"]
I3Stack["Full Stack
(API, Agents, DB, Vector Store)"]
I3Proxy["LiteLLM Proxy"]
I3Stack --- I3Proxy
end
I1Proxy -->|HTTPS| Backend
I2Proxy -->|HTTPS| Backend
I3Proxy -->|HTTPS| Backend
classDef default font-size:16px,padding:20px
```
Each instance has their own LiteLLM proxy (independent cost tracking, versioning, configuration). All instance LiteLLM
proxies connect to shared LLM backend resources (Swiss LLM Cloud or local vLLM). Prompts, responses, and user data stay
within instance boundaries.
______________________________________________________________________
## Security considerations
### Instance isolation
Instances do not communicate with each other. Each instance has separate databases, vector stores, and file storage.
Each instance connects to their own IdP (Azure AD, Keycloak) or can share a common IdP with separate namespace
isolation. LiteLLM enforces per-instance API keys and quotas.
### LLM proxy security
LiteLLM does not persist prompts or responses (stateless operation). API key management includes secure key generation,
rotation, and revocation. Per-instance request limits prevent abuse. All LLM requests are logged with instance ID but
without prompt content. Presidio integration is optional for PII detection and redaction.
### Data in transit
All communication is encrypted with TLS (instance to LLM proxy). Certificate management uses Let's Encrypt for
production and mkcert for development. API authentication uses bearer tokens (OAuth 2.0, JWT).
### Data at rest
PostgreSQL uses transparent data encryption (TDE). Persistent volumes are encrypted (LUKS, Azure Disk Encryption).
Secrets are managed via environment variables, Azure Key Vault, or Docker secrets.
______________________________________________________________________
## Next steps
- [Multi-tenancy](../../16_multi_tenancy/) - Logical separation within a single instance
- [Production Configuration](../2_production_configuration/) - Configuration guide for production deployments
- [Scaling Considerations](../3_scaling_considerations/) - Scaling instances
- [Backup and Recovery](../4_backup_and_recovery/) - Backup strategies for per-instance architecture
- [Updates and Maintenance](../6_updates_and_maintenance/) - Managing updates across multiple instances
______________________________________________________________________
## FAQ
::: details Can instances share agents or pipelines?
No. Each instance has its own isolated set of agents and pipelines. However, the same agent definitions (code) can be
deployed across multiple instances. Customizations are instance-specific.
For sharing agents within an organization, use [multi-tenancy](../../16_multi_tenancy/) to create logical boundaries
within a single instance.
:::
::: details What's the difference between multi-instancing and multi-tenancy?
**Multi-instancing** (this chapter) means running multiple completely isolated Swiss AI Hub installations. Each has
separate databases, vector stores, and application servers. Even a misconfiguration cannot cause data leakage between
instances. Use this when you need absolute isolation (e.g., different legal entities, highly sensitive departments).
**Multi-tenancy** ([chapter 15](../../16_multi_tenancy/)) means creating organizational boundaries within a single Swiss
AI Hub instance. Multiple tenants share infrastructure but have logical separation through access control. Use this for
departments, projects, or customers within the same organization.
You can combine both: Run multiple instances (hard isolation) where each instance uses multi-tenancy (flexible
separation within that instance).
:::
::: details What data does the shared LLM backend see?
Each instance has their own LiteLLM proxy, so prompts and responses stay within the instance. The shared LLM backends
(Swiss LLM Cloud or local vLLM) see API requests from multiple instance LiteLLM proxies (stateless, not persisted),
model inference requests (prompts and completions in transit only), no instance identification or context, and anonymous
PII data if enabled.
They do not see which instance made the request, conversational history, or any stored data. All context remains in the
instance's LiteLLM proxy and database.
:::
::: details Can an instance use self-hosted models exclusively?
Yes. For air-gapped or fully on-premise deployments, use the GPU variant of the docker-compose file. All inference runs
locally via vLLM on an NVIDIA RTX 6000 Pro (96 GB VRAM) with no outbound internet connectivity required.
:::
::: details How are costs tracked per instance?
LiteLLM tracks API usage per instance and user: token counts (input/output), model usage (GPT-4, Gemini, etc.), cost
calculations based on model pricing, and monthly budget enforcement.
Data is available in the LiteLLM admin UI and exportable for billing.
:::
::: details Can instances have different LLM access?
Yes. LiteLLM configuration allows per-instance model access. For example, Instance A might use Swiss LLM Cloud with a
specific set of models, Instance B might use a different model selection for flexibility, and Instance C might use local
vLLM only for air-gapped deployment.
:::
::: details What happens if the LLM proxy is unavailable?
Instances will experience LLM-dependent feature degradation. RAG agents cannot generate responses. Embeddings cannot be
created for new documents. However, existing data and UI remain accessible, and non-LLM features (document upload, RBAC,
observability) continue working.
Mitigation: Deploy LiteLLM with high availability (multiple replicas, load balancing).
:::
::: details How do you manage updates across many instances?
See [Updates and Maintenance](../6_updates_and_maintenance/) for strategies including phased rollouts (pilot to
production), blue-green deployments, automated update orchestration (Ansible, Kubernetes operators), and per-instance
update schedules.
:::
## Related documentation
- [Multi-tenancy](../../16_multi_tenancy/) - Creating organizational boundaries within an instance
- [Core Components](../../../1_vision_and_positioning/3_core_components/) - Swiss AI Hub architecture
- [Authentication & Authorization](../../11_access_management/1_authentication_setup/) - Authentication configuration
- [Monitoring and Alerting](../5_monitoring_and_alerting/) - Observability for multi-instance deployments
- [Swiss Data Protection](../../21_compliance/3_dsg/) - revDSG compliance for public sector
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/2_production_configuration.md
title: "Production Configuration"
description: ""
---
@mhoegger
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/3_scaling_considerations.md
title: "Scaling Considerations"
description: ""
---
# Scalability
The Swiss AI Hub implements scalability as a core architectural principle, enabling organizations to grow their AI
capabilities from pilot projects to enterprise-wide deployments without fundamental architectural changes. The
platform's design ensures that scaling capacity requires only operational adjustments - deploying additional instances -
rather than code modifications or architectural redesigns.
## Horizontal Scalability Through Event-Driven Architecture
The event-driven architecture enables horizontal scalability and system resilience:
**Stateless Agent Code**: Agent logic contains no mutable state, enabling any instance to process any event. This
stateless design eliminates the coordination overhead typically required in distributed systems, allowing new instances
to begin processing work immediately upon deployment without synchronization or state transfer.
**Load Distribution**: Events distributed across multiple agent instances for parallel processing. The messaging
infrastructure automatically balances work across available instances, ensuring optimal resource utilization without
requiring manual load balancing configuration. Organizations can adjust capacity by simply changing the number of
running instances.
**Failure Recovery**: Failed operations retried without state loss through event replay. When an agent instance fails
during processing, another instance can immediately resume work by replaying the event history. This resilience model
ensures no work is lost and no manual intervention is required for recovery.
**System Evolution**: New event types introduced without modifying existing components. The automatic event type
registration and graceful degradation mechanisms allow the platform to evolve continuously. Organizations can deploy new
capabilities incrementally, with different versions operating concurrently during migration periods.
## Operational Scaling
Organizations can scale agent capacity by deploying additional instances without architectural changes. The platform
supports multiple scaling dimensions:
**Compute Scaling**: Deploy additional agent instances to handle increased processing volume. Each instance operates
independently, consuming events from shared streams and processing work in parallel with other instances.
**Geographic Distribution**: Agent instances can operate in different geographic regions without requiring shared state.
The messaging infrastructure ensures event delivery regardless of physical location, enabling global deployments that
reduce latency for distributed user populations.
**Incremental Upgrades**: System upgrades deploy incrementally without service interruption. Organizations can deploy
new agent versions alongside existing versions, gradually shifting traffic to updated implementations while maintaining
the ability to roll back if issues arise.
### Horizontal Scalability
The event-driven architecture enables effortless scaling to meet fluctuating demand. When system load increases,
additional worker instances can be deployed to process events from the same streams without any modifications to the
application code or architecture. These workers automatically distribute the processing load by consuming events in
parallel.
This approach provides several operational advantages: capacity can be increased dynamically during peak periods and
reduced during quiet times, system performance remains consistent as workload grows, and there are no bottlenecks from
centralized processing. Since event processing is stateless, each worker operates independently - if one fails, others
continue processing, and the failed worker can be restarted without impacting ongoing operations.
Organizations can scale specific components based on actual demand patterns. If agent execution requires more capacity,
additional agent workers can be deployed. If data ingestion becomes a bottleneck, more pipeline workers can be added.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/4_backup_and_recovery.md
title: "Backup and Recovery"
description: ""
---
# Backup and Recovery
## Overview
Swiss AI Hub includes an automated backup service that periodically dumps all stateful services to the internal
SeaweedFS S3 storage (`s3://backups/`). Backups run on a daily schedule (1 AM Europe/Zurich) with automatic retention
cleanup. The backup service is a standalone Dagster instance with a web UI for monitoring, manual triggers, and
parameterized restores.
Each instance has independent backups. Data isolation between instances. Recovery operations don't affect other
instances.
::: info Multi-instancing context
This chapter assumes a multi-instance deployment model where each organization has their own isolated Swiss AI Hub
instance. For multi-tenancy (logical separation within a single instance), see [Multi-tenancy](../../16_multi_tenancy/).
:::
______________________________________________________________________
## What gets backed up
| Service | Method | Data |
| --------------------- | -------------------------------------------------------- | ------------------------------------------------- |
| PostgreSQL (main) | `pg_dumpall` + `pg_dump` | OpenWebUI, Langfuse, Dagster, LiteLLM databases |
| PostgreSQL (FerretDB) | `pg_dumpall` + `pg_dump` + `COPY` (DocumentDB catalog\*) | Agent configs, users, threads, tokens, RBAC roles |
| Milvus | `milvus-backup` (official tool) | Vector collections with consistent metadata |
| Neo4j | `neo4j-admin` via temp container | Agent memory graphs (Mem0) |
| ClickHouse | `BACKUP TO Disk('backup_s3', ...)` SQL command | Langfuse traces, observations, scores |
| Valkey | `BGSAVE` + RDB copy (+ temp container on restore) | Cache and session state (RDB snapshot) |
| NATS | `nats` CLI stream backup | JetStream streams |
### What is NOT backed up by the platform
**SeaweedFS bucket data** (user-uploaded documents, knowledge base files, chat attachments) is the responsibility of the
infrastructure layer. Use VM snapshots, rclone sync, or external S3 replication to protect this data. The platform
cannot back up SeaweedFS into itself.
`parse-cache` is derived data: it holds MinerU results that are re-created on demand, so it needs no backup.
`sandbox-files` is **not** derived data. It mirrors the code-sandbox homes (the users' **My Files**) so the files
survive the loss of the sandbox volume. Neither the sandbox volume nor this bucket is covered by the platform backup, so
the bucket is the only copy of My Files that outlives that volume. Include it when you protect SeaweedFS.
All service backups are required. A missing backup for any service will block the restore.
______________________________________________________________________
## Configuration
Configure the backup service via environment variables in `.env.dev` (development) or `.env.prod` (production):
```bash
BACKUP_RETENTION_DAYS="7" # Keep backups for N days (dev: 7, prod: 30)
BACKUP_MINIMUM_KEEP="3" # Minimum backups preserved regardless of age
BACKUP_S3_BUCKET="backups" # S3 bucket name for backup storage
```
The backup schedule (daily at 1 AM Europe/Zurich) is defined in Dagster and can be toggled on/off via the Dagster UI.
::: warning Backup and pipeline schedules must not overlap
The backup stops all application containers, including the pipeline Dagster instance. The default schedules are
staggered: backup at 1:00 AM, pipeline observation at 2:00 AM, pipeline cleanup at 3:00 AM. If you change any schedule,
ensure the backup finishes before the first pipeline job starts — a backup running during a pipeline job kills it
mid-execution.
:::
______________________________________________________________________
## How backups work
Every backup stops all managed containers in parallel before taking snapshots, guaranteeing transactional consistency
across all databases. Containers with the prefixes `backup-`, `seaweedfs-`, `etcd`, and `traefik` are excluded from the
stop/start cycle — SeaweedFS is needed for S3 access, etcd for Milvus metadata, and Traefik for ingress availability
during backups.
Each service is dumped using its native backup tool. After all services are backed up, the platform restarts all
previously running containers in parallel. Docker Compose restart policies ensure services converge to a healthy state
even if some start before their dependencies are ready. If the backup fails mid-run, a failure hook automatically
restarts all managed containers as a safety measure.
To trigger a manual backup, open the Dagster UI at `http://localhost:3004`, navigate to the backup assets, and click
"Materialize".
### Neo4j sibling container
Neo4j Community Edition does not support online backups — `neo4j-admin database dump` requires exclusive access to the
`/data` directory and cannot run while the Neo4j process holds a lock on it. Because a stopped Docker container cannot
execute commands either, the backup service spins up a **temporary sibling container** using the same Neo4j image and
the same `/data` volume (both discovered automatically from the production container at runtime). The sibling runs
`neo4j-admin`, copies the dump file out, and is removed immediately afterward. A similar sibling is used for restore.
You may notice a short-lived container named `neo4j-dump-` or `neo4j-restore-` during backup/restore runs — this
is expected and cleaned up automatically.
### \* DocumentDB catalog workaround
PostgreSQL's `pg_dump` silently skips data for tables owned by extensions — it assumes `CREATE EXTENSION` will
repopulate them during restore. The DocumentDB extension (used by FerretDB's PostgreSQL backend) owns its catalog tables
(`documentdb_api_catalog.collections` and `collection_indexes`) but does not register them for dump inclusion. The usual
fix (`pg_extension_config_dump()`) cannot be called externally — PostgreSQL restricts it to `CREATE EXTENSION` scripts.
Without a workaround, a restore would have all document data intact but an empty catalog — FerretDB would report zero
collections. The backup service handles this automatically: during backup it separately extracts catalog rows using
`COPY TO STDOUT` into an `ext-catalog.sql.gz` artifact, and during restore it replays this SQL after `pg_restore`. No
operator action is required.
______________________________________________________________________
## Listing backups
Open the Dagster UI at `http://localhost:3004` to see the backup asset view, which shows backup history at a glance. The
asset metadata includes timestamp and S3 prefix for each backup.
______________________________________________________________________
## Recovery
### Full-system restore
Restores the entire platform to a specific backup. Stops all services, restores each database, then restarts all
containers.
To run a full restore, open the Dagster UI at `http://localhost:3004`, navigate to Jobs -> `full_restore_job`, select a
backup timestamp from the partition dropdown, and click "Launch Run".
The restore process follows three phases:
1. **Full stop**: All application and database containers are stopped (except SeaweedFS, which is needed for S3 access)
2. **Restore data**: Each service is restored from its backup. PostgreSQL instances are started temporarily for SQL
import. Milvus is started temporarily for the milvus-backup restore API.
3. **Full start**: All previously running containers are restarted. Docker Compose restart policies ensure services
converge to a healthy state even if some start before their dependencies are ready.
::: warning Restore failure behavior
On failure during restore, containers are intentionally **not** restarted automatically. The operator must investigate
the failure and decide whether to retry or restore from a different backup. This is a deliberate safety measure — an
automatic restart after a partial restore could leave the system in an inconsistent state.
:::
::: warning A backup taken before a Langfuse upgrade is not a rollback target
The restore validates only that the expected artifacts are **present**. It carries no schema-version stamp, so it will
happily restore a backup taken under an older Langfuse into a stack running a newer one.
Langfuse spans two stores that are restored by independent handlers: its Postgres database (`langfuse.dump`) and its
ClickHouse tables (`clickhouse/`). ClickHouse is captured with a native `BACKUP DATABASE`, so the restore reinstates the
**table definitions** as well as the data. Nothing checks that the two stores agree with each other, and no migration
step runs afterwards — the schema only moves forward again when the Langfuse containers next start.
Treat a Langfuse version bump as a one-way door: take a backup beforehand for data recovery, but plan forward recovery
rather than downgrade.
:::
### Users' sandbox files (My Files)
The users' homes in the code sandbox (the files behind My Files) live on the host volume mounted at
`/open-terminal`. The `sandbox-mirror` service copies them one way into the `sandbox-files` bucket every
`SANDBOX_MIRROR_INTERVAL_SECONDS`, keeping each file's owner, group, mode and modification time, and keeps a deleted or
overwritten file under `.deleted//` for a week. The mirror protects against losing the homes volume, not
SeaweedFS itself; see [What is NOT backed up](#what-is-not-backed-up-by-the-platform).
To bring the homes back after the volume is lost:
1. Stop the mirror **first**, then the sandbox. A running mirror would copy the empty volume over the bucket and move
every file into `.deleted/`.
```bash
docker compose stop sandbox-mirror open-terminal
```
2. Copy the homes back, with the mirror's own credentials and the volume mounted writable:
```bash
docker compose run --rm --no-deps \
-v "/open-terminal:/restore" \
--entrypoint rclone sandbox-mirror \
copy mirror:sandbox-files /restore --metadata --exclude "/.deleted/**"
```
Files come back with their owner and mode. Folders do not: the bucket holds no folder metadata, so each home comes
back owned by `root` with mode `755` until the next step.
3. Recreate the sandbox, then start the mirror again. The new container provisions each user's account on their next
request, which makes them the owner of their home again and sets it to `2770`, so only they can read it.
```bash
docker compose up -d --force-recreate open-terminal
docker compose up -d sandbox-mirror
```
Skipping the recreate leaves every home owned by `root`: users cannot write to their own files, and other users can
list them.
______________________________________________________________________
## VM snapshots
VM snapshots remain a valid complementary strategy, especially for protecting SeaweedFS data. They capture everything:
OS, Docker, data, configuration. You restore the entire VM in one operation.
Stop Swiss AI Hub services before creating a snapshot using `docker compose down`. Alternatively, use
application-consistent snapshots (Azure with VM agent, VMware with quiesce). Create snapshots before major updates.
______________________________________________________________________
## Continuous Postgres maintenance
The same Dagster instance that runs backup also runs **continuous Postgres health maintenance** so the platform's
`event_logs` and `runs` tables don't grow without bound on long-running deployments. Two additional jobs are wired into
the same backup Dagster UI at `http://localhost:3004`:
- **`dagster_cleanup_job`** — Sundays at 3 AM Europe/Zurich. Prunes verbose Python logs and curated framework-internal
events (`HANDLED_OUTPUT`, `LOADED_INPUT`, `ENGINE_EVENT`, `ASSET_MATERIALIZATION_PLANNED`, `STEP_OUTPUT`) past their
retention windows. Idempotently ensures the cleanup query indexes exist and applies tighter autovacuum tuning to the
heavy tables.
- **`postgres_repack_job`** — first Sunday of each month at 4 AM. Runs `pg_repack` on `event_logs`, `runs`, and
`job_ticks` to return disk pages to the OS (plain `VACUUM` only marks dead rows reusable internally).
**UI-safe by construction**: cleanup never deletes rows the Dagster UI depends on (`ASSET_MATERIALIZATION`,
`STEP_SUCCESS`, `STEP_FAILURE`, `RUN_SUCCESS`, `RUN_FAILURE`, the `runs` table, asset catalog, sensor cursors).
**Mutually exclusive with backup**: every job that touches Postgres carries a `postgres-mutex` tag. The backup Dagster's
run coordinator caps concurrency for that tag at one, so cleanup or repack ticks queue behind a still-running backup
instead of starting concurrently. Within each run, intra-run parallelism (e.g. parallel per-service backups) is
unaffected.
**`pg_repack` ships in the platform Postgres image**: the project-managed image extends `pgvector/pgvector:pg17` with
`postgresql-17-repack` and the extension is registered in the `dagster` database on first init. Deployments using a
foreign Postgres image without the extension still work — repack reports a clean skip in the run metadata; cleanup works
unconditionally.
### Configuration
```bash
# Retention windows (defaults follow the official Dagster docs recipe)
DAGSTER_DEBUG_LOG_RETENTION_DAYS="7"
DAGSTER_INFO_LOG_RETENTION_DAYS="60"
DAGSTER_WARNING_LOG_RETENTION_DAYS="60"
DAGSTER_UNIMPORTANT_EVENT_RETENTION_DAYS="30"
# Per-DELETE row cap — protects against WAL-flooding on first run against a backlogged DB
DAGSTER_CLEANUP_BATCH_LIMIT="1000000"
# Kill switch — set to true and the maintenance handlers no-op; backup is unaffected
MAINTENANCE_DISABLED="false"
DAGSTER_DB="dagster"
POSTGRES_PORT="5432"
```
A heavily backlogged DB drains over multiple weekly ticks (`DAGSTER_CLEANUP_BATCH_LIMIT` rows per tick × 4 cleanup
handlers). Operators wanting a faster initial drain can manually launch `dagster_cleanup_job` repeatedly through the
Dagster UI.
______________________________________________________________________
## Backup storage layout
Each backup is stored in a flat, timestamped directory:
```
s3://backups/
2026-02-17_02-00-00/
postgres-main/
globals.sql.gz
openwebui.dump
langfuse.dump
dagster.dump
litellm.dump
postgres-ferretdb/
globals.sql.gz
ferretdb.dump
ext-catalog.sql.gz
milvus_backup_2026_02_17_02_00_00/...
neo4j.dump
clickhouse/
backup_2026_02_17_02_00_00/...
valkey.rdb
nats-jetstream.tar.gz
2026-02-18_02-00-00/
...
```
______________________________________________________________________
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/5_monitoring_and_alerting.md
title: "Monitoring & Alerting"
description: ""
---
# Monitoring & Alerting
A production AI system must be transparent, reliable, and predictable. While Day 1 is about impressive demos, Day 2 is
about maintaining trust through operational excellence. The Swiss AI Hub provides a comprehensive, built-in
observability suite to give you a complete picture of your platform's health, performance, and cost.
This section explains the layers of monitoring and alerting built into the platform. You'll learn what we measure, how
you can visualize it, and how the system proactively notifies you of issues.
## The Pillars of Observability
The platform's monitoring philosophy is built on the industry-standard pillars of observability, providing answers to
critical operational questions.
### 1. Health Checks: "Is it working right now?"
Health checks are the platform's heartbeat, continuously verifying that every component is alive and functional. Unlike
metrics or logs, they provide a simple, immediate answer to the most fundamental question.
The platform uses a multi-layered approach:
- **Native Docker Checks**: Automatically monitor if service processes are running and responsive. Docker can restart
unhealthy containers, enabling self-healing for transient issues.
- **Application Endpoint Checks**: Services expose a liveness endpoint (`/health`) that answers for as long as the
process is serving requests. The API additionally exposes a readiness endpoint (`/ready`) that reports each backing
dependency - NATS, MongoDB, Redis, Milvus and S3 - as its own flag.
- **Synthetic Probes**: For services without native health endpoints, the platform actively polls them to ensure they
are available and responsive.
Every health status change - from healthy to unhealthy, service starts, and stops - is captured as a structured event,
providing a complete historical record of service availability.
#### Degraded dependencies are reported, not restarted
Services start and keep serving when a backing store is temporarily unreachable, instead of failing their startup and
being restarted into the same unavailable dependency. The vector database is the clearest example: Milvus reports itself
healthy several minutes before its query proxy accepts connections, so the API and the agents boot without a vector
store connection and open one on the first request that needs it.
The operational consequence is that a degraded dependency does **not** appear as an unhealthy or restarting container.
It appears in the readiness payload:
```bash
curl -s http://localhost:8000/api/v1/health/ready
```
```json
{
"status": "unhealthy",
"code": 503,
"version": "0.320.0",
"checks": { "nats": true, "mongodb": true, "redis": true, "milvus": false, "s3": true }
}
```
Alerting must therefore watch `/ready` - both its status code and the individual `checks` flags - and not container
health alone, which would report the platform as fine while vector search is unavailable.
### 2. Metrics: "How is it performing?"
Metrics are quantitative measurements that track performance and resource utilization over time. They are essential for
trend analysis, capacity planning, and identifying performance bottlenecks before they impact users.
The platform automatically collects key metrics across two main categories:
- **Infrastructure Metrics**: Container-level data for every service, including CPU utilization, memory consumption,
network traffic, and disk I/O. This provides a clear view of resource usage and helps in cost management and capacity
planning.
- **Application Metrics**: (In progress) As services are instrumented, they will emit detailed performance data, such as
API request latency, error rates, AI agent execution times, and document processing throughput.
These metrics provide the data needed to optimize performance, forecast budgets, and make informed decisions about
scaling your infrastructure.
### 3. Logs: "What happened and why?"
Logs provide a detailed, chronological record of every event that occurs within the platform. When an issue arises, logs
are the primary tool for root cause analysis, offering the context needed to understand exactly what happened.
The platform captures logs from multiple sources:
- **Application Logs**: Structured output from all Python services, including informational messages, warnings, and
critical errors.
- **Container Logs**: All `stdout` and `stderr` output from every container, capturing everything from startup messages
to unhandled exceptions.
- **Request Logs**: Records of HTTP requests and their outcomes.
- **Security Logs**: Audit trail of authentication events, access attempts, and permission checks.
All logs are centralized, structured, and searchable, allowing you to quickly diagnose problems, audit activity, and
analyze usage patterns.
## Dashboards: The Unified View
Data is only useful if you can understand it. The Swiss AI Hub uses **SigNoz**, an open-source, OpenTelemetry-native
platform, as its central observability backend. It provides a single, unified interface to visualize all your health,
metric, and log data.
Out-of-the-box, you get access to several key dashboards:
- **Infrastructure Overview**: A high-level view of CPU, memory, and network utilization across all services, plus a
real-time matrix of service health.
- **AI Operations Dashboard**: Specialized view of AI activities, including model usage, token consumption, query
latency, and cost-per-operation tracking.
- **Application Performance**: User-facing service quality metrics, such as API response times, request volumes, and
error rates.
- **Log Analysis**: A powerful interface to search, filter, and analyze log data from every component in the platform.
::: details Specialized Service Dashboards
For deeper insights, the platform also includes built-in dashboards for specific infrastructure components:
- **Traefik (Reverse Proxy)**: Visualizes request routing, service health, and TLS certificate status.
- **Langfuse (LLM Observability)**: Traces every LLM operation, showing token usage, latency, cost attribution, and full
prompt/response context. Also provides dataset management and experiment evaluation.
- **Dagster (Workflow Engine)**: Monitors the status, history, and performance of all data ingestion and processing
pipelines.
:::
## Alerting: Proactive Notifications
While dashboards are for pulling information, alerting proactively pushes critical information to you. It turns your
observability data into automated notifications, ensuring you're aware of issues often before your users are.
The alerting system is highly flexible and configured within your observability platform (e.g., SigNoz), not hardcoded
into the Swiss AI Hub. This allows you to tailor notifications to your organization's specific needs. You can configure
alerts for:
- **Critical Service Failures**: Immediate notification if a core service like the API gateway or database becomes
unhealthy.
- **Performance Degradation**: Alerts when API response times exceed targets or error rates begin to climb.
- **Resource Limits**: Proactive warnings when CPU, memory, or storage utilization approaches capacity limits.
- **Cost Management**: Notifications when AI token consumption or cloud spending approaches predefined budget
thresholds.
- **Security Events**: Alerts for suspicious activity, such as repeated failed login attempts.
Alerts can be routed to various channels, including email, Slack, Microsoft Teams, and incident management platforms
like PagerDuty.
## The Observability Foundation: OpenTelemetry
The entire monitoring and alerting system is built on **OpenTelemetry (OTel)**, a CNCF-graduated, vendor-neutral
standard for observability.
This is a deliberate architectural choice with significant benefits:
- **No Vendor Lock-in**: The platform emits data in a standard format. While SigNoz is the default, you are free to send
telemetry to any OTel-compatible backend - be it Grafana, Datadog, Splunk, or your existing enterprise monitoring
tool. Adding a new destination is a configuration change, not a re-instrumentation project.
- **Unified Data**: OTel provides a consistent way to collect metrics, logs, and traces. This means all your data is
automatically correlated, allowing you to seamlessly pivot from a performance metric spike to the exact logs and
traces that explain it.
- **Future-Proof**: By building on an industry standard, the platform benefits from the continuous innovation of the
entire observability community.
All telemetry flows through a central **OpenTelemetry Collector** within the platform. This component receives data from
all services, enriches it with useful metadata, and securely exports it to your chosen destination(s). This architecture
ensures that you have complete control and ownership over your observability data, just as you do with the rest of the
platform.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/5_monitoring_and_alerting/1_log_aggregation.md
title: "External Log Aggregation"
description: ""
---
# External Log Aggregation
The Swiss AI Hub's observability architecture is built on **OpenTelemetry**, enabling you to export logs, metrics, and
traces to external systems for centralized management, long-term retention, and advanced analytics. While the platform
is pre-configured to work with **SigNoz** as the officially supported backend, the OpenTelemetry foundation ensures
you're never locked into a single vendor.
## Architecture Overview
All telemetry flows through a central **OpenTelemetry Collector** that acts as a processing and routing hub:
```mermaid
graph TB
Services["Swiss AI Hub Services
(API, Agents, Web)"]
Collector["OTel Collector
(Processor & Router)"]
Langfuse["Langfuse
(LLM Observability)"]
SigNoz["SigNoz
(Official Backend)"]
External["External Systems
(Grafana, Datadog, Splunk)"]
Services -->|"OTLP Protocol"| Collector
Collector -->|"OpenInference Spans"| Langfuse
Collector -->|"Logs, Metrics, Traces"| SigNoz
Collector -.->|"Optional"| External
classDef default font-size:16px,padding:20px
```
The collector receives telemetry via the **OpenTelemetry Protocol (OTLP)**, processes it (filtering, batching,
enriching), and exports it to one or more backends. This architecture provides several key benefits:
- **Centralized Control**: A single point for configuring data flows and transformations
- **Performance**: Batching and compression reduce network overhead
- **Flexibility**: Route different data types to different backends
- **Resilience**: Built-in retries and queuing handle temporary outages
## SigNoz: The Official Backend
**SigNoz** is the officially supported observability backend for the Swiss AI Hub. It is an open-source,
OpenTelemetry-native platform that provides unified logs, metrics, and traces in a single interface.
### Why SigNoz?
- **OpenTelemetry Native**: Built from the ground up on OTel standards
- **Unified Observability**: Logs, metrics, and traces in one platform
- **Cost-Effective**: Open-source with predictable pricing
- **Full-Text Search**: Powerful log querying and filtering
- **Distributed Tracing**: End-to-end request flow visualization
- **Custom Dashboards**: Pre-built and customizable visualizations
- **Flexible Alerting**: Multi-channel notifications (email, Slack, Teams, PagerDuty)
### Deployment Options
The platform supports two SigNoz deployment models:
#### SigNoz Cloud (Easiest)
SigNoz offers a fully managed cloud service with regional endpoints (EU, US, IN). The Swiss AI Hub is pre-configured to
use SigNoz Cloud - you only need to provide your ingestion key and region endpoint via environment variables:
```bash
OTEL_CLOUD_ENDPOINT="ingest.eu.signoz.cloud:443"
OTEL_CLOUD_HEADERS="{'signoz-ingestion-key':}"
```
#### Self-Hosted SigNoz (Production Recommended)
For production deployments, **self-hosting SigNoz on a dedicated VM** is strongly recommended for several reasons:
- **Performance Isolation**: Observability overhead doesn't affect application performance
- **High Availability**: Application continues running even if monitoring fails
- **Data Sovereignty**: Full control over telemetry data location and retention
- **Security**: Network isolation between application and observability layers
```mermaid
graph TB
subgraph AppVM["Swiss AI Hub Application Infrastructure"]
Services["Services"]
OTel["OTel Collector"]
Services --> OTel
end
subgraph ObsVM["Observability Infrastructure (Separate)"]
SigNozStack["SigNoz Stack
(Frontend, Query Service, ClickHouse)"]
end
OTel -->|"OTLP/gRPC
Port 4317"| SigNozStack
classDef default font-size:16px,padding:20px
```
SigNoz can be deployed using Docker Compose on a separate VM with appropriate resources (4+ CPU cores, 8+ GB RAM, 100+
GB storage). The Swiss AI Hub's OTel Collector is then configured to point to the self-hosted endpoint instead of SigNoz
Cloud.
## Data Collection
The platform automatically collects and exports:
### Logs
- **Application Logs**: Structured JSON logs from all Python services (INFO, WARNING, ERROR, CRITICAL)
- **Container Logs**: All stdout/stderr output from Docker containers
- **Access Logs**: HTTP requests and responses from the API gateway
- **Security Logs**: Authentication events and permission checks
### Traces
- **Distributed Traces**: End-to-end request flows across services (API → Agent → LLM → Database)
- **OpenInference Traces**: LLM-specific spans with prompt/response content, token usage, and costs
::: info Dual Tracing Strategy
OpenInference traces are sent to **both** Langfuse (local, specialized LLM debugging) and SigNoz (cloud, long-term
storage and correlation). This dual approach provides immediate debugging capabilities while maintaining comprehensive
observability.
:::
### Metrics
- **Infrastructure Metrics** (planned): CPU, memory, network, disk I/O per container
- **Application Metrics** (planned): API latency, error rates, agent execution times
- **Business Metrics**: Active sessions, document processing throughput, cost per operation
## Configuration
The OTel Collector is configured via `/configs/otel/otel-collector-config.dev.yaml`. The default configuration includes:
- **Generic Cloud Exporter**: Configured via environment variables for flexibility
- **Filtering**: Removes noisy health check and database spans
- **Batching**: Optimizes network usage by batching telemetry
- **Retry Logic**: Handles temporary network failures
- **Compression**: Reduces bandwidth with gzip compression
All backends are configured via environment variables, making it easy to switch between SigNoz Cloud, self-hosted
SigNoz, or alternative backends without modifying code.
## Alternative Backends
While **SigNoz is the officially supported backend**, the OpenTelemetry foundation allows you to send data to any
OTel-compatible system. To use an alternative backend, update the environment variables to point to your chosen system's
OTLP endpoint. Some backends may require additional exporter configuration in the OTel Collector config file.
______________________________________________________________________
## Next Steps
- Explore the [SigNoz documentation](https://signoz.io/docs/) for query builders and alert configuration
- Review the [OpenTelemetry Collector documentation](https://opentelemetry.io/docs/collector/) for advanced
configuration
- Configure [Langfuse LLM Observability](../../../10_chat_ui/10_observability/) for AI-specific debugging
- Set up [Cost Tracking](../../../14_cost_control/) for LLM usage monitoring
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/6_updates_and_maintenance.md
title: "Updates & Maintenance"
description: ""
---
# Updates and maintenance
## Architecture
The Swiss AI Hub separates core platform components from customer-specific code. The core platform (this repository)
contains shared foundation components like API, Web, Dagster, and Bot. Customer repositories contain custom agents,
pipelines, and processes. Both use independent semantic versioning and can be updated separately.
Customer code pins to a specific core version through `pyproject.toml`:
```toml
[project.dependencies]
swiss-ai-hub = { git = "https://github.com/bbvch-ai/aihub-core.git", tag = "v1.2.3" }
```
This means core updates don't automatically affect customer deployments. Customers control when they adopt new core
versions.
______________________________________________________________________
## Versioning
The core platform uses semantic versioning:
- Major (X.0.0): Breaking changes and architectural updates
- Minor (0.X.0): New features, backward-compatible changes
- Patch (0.0.X): Bug fixes and security patches
Three version tags are available:
| Tag | Description | Stability |
| --------- | ------------------------ | --------- |
| `latest` | Latest stable release | High |
| `nightly` | Latest development build | Medium |
| `v1.2.3` | Specific version tag | Highest |
Customer code uses its own independent version numbers.
### Release process
When a PR merges to `main` with a version label (`major`, `minor`, or `patch`), CI/CD computes the new version, creates
a Git tag, and builds all affected services. Docker images are published to `ghcr.io/bbvch-ai/aihub-core/*` with the
version tag. A changelog is generated automatically.
Each release also publishes self-contained deployment bundles as GitHub Release assets:
- `swissaihub-.tar.gz` — CPU-only deployment bundle
- `swissaihub--gpu.tar.gz` — GPU-enabled deployment bundle
These bundles contain everything needed to deploy: `docker-compose.yml` with version-pinned image tags, all service
configuration files, an `.env.template` with placeholder secrets, and a `setup-env.sh` script that generates a `.env`
file with cryptographically secure random values for all passwords, tokens, and signing keys.
Example core images:
```
ghcr.io/bbvch-ai/aihub-core/api:v1.2.3
ghcr.io/bbvch-ai/aihub-core/dagster:v1.2.3
ghcr.io/bbvch-ai/aihub-core/web:v1.2.3
```
Customer code follows the same CI/CD pattern:
```
ghcr.io/bbvch-ai/aihub-core-/agent:v1.2.3
ghcr.io/bbvch-ai/aihub-core-/pipeline:v1.2.3
```
Browse all releases at [github.com/bbvch-ai/aihub-core/releases](https://github.com/bbvch-ai/aihub-core/releases).
______________________________________________________________________
## Updates
### Core platform updates
Download the new release bundle from [GitHub Releases](https://github.com/bbvch-ai/aihub-core/releases) and extract it
alongside your current deployment:
```bash
# Download the new version
VERSION="v1.3.0"
curl -L "https://github.com/bbvch-ai/aihub-core/releases/download/${VERSION}/swissaihub-${VERSION}.tar.gz" \
| tar -xz -C /tmp/swissaihub-update
# Copy your existing .env into the new bundle
cp .env /tmp/swissaihub-update/.env
# Review any new environment variables added in the release
diff <(grep -oP '^[A-Z_]+=' .env.template) <(grep -oP '^[A-Z_]+=' /tmp/swissaihub-update/.env.template)
# Replace the deployment files
cp -r /tmp/swissaihub-update/* .
# Pull new images and restart
docker compose pull
docker compose up -d
```
Backward-compatible updates (patch and minor versions) only require pulling new images and restarting. The release
bundle's `docker-compose.yml` already references the correct version-pinned image tags. Customer code continues running
unchanged.
Major core updates with breaking changes require coordinated updates. Customer code must be updated to work with the new
core version. Both core and customer code are updated together during a maintenance window.
### New storage buckets
`seaweedfs-init` creates every missing SeaweedFS bucket each time it runs, and `docker compose up -d` starts it again on
every run, so the update above creates the buckets a release adds. Only an update that starts selected services, such as
`docker compose up -d ` or `--no-deps`, skips it. Then run it yourself:
```bash
docker compose up seaweedfs-init
```
A missing bucket fails at runtime, not at startup. Without the `parse-cache` bucket, every MinerU document parse fails
with `NoSuchBucket`. The bucket caches parsed documents by content hash, and entries expire after 7 days. An expired
entry is parsed again, so nothing needs a backup.
The `sandbox-files` bucket holds the one-way mirror of every user's code-sandbox home (the files behind **My Files**).
It is created the same way, and needs `SANDBOX_MIRROR_S3_ACCESS_KEY`, `SANDBOX_MIRROR_S3_SECRET_KEY` and
`SANDBOX_MIRROR_INTERVAL_SECONDS` in your `.env` (the review step above shows them). The mirror uses an S3 identity
limited to this bucket, so recreate `seaweedfs-s3` for the identity to take effect. Files deleted or overwritten in the
sandbox are kept under `.deleted/` for 7 days.
### Customer code updates
Customer code can be updated independently when the core version pin remains unchanged. Update the customer image tags
in `docker-compose.yml`, pull the new images, and restart the customer services.
When customer code adopts a new core version, update the core version pin in `pyproject.toml`, rebuild the customer
images, then deploy both core and customer updates together.
______________________________________________________________________
## Rollbacks
### VM snapshots
VM snapshots capture the entire system state. Rollback restores the complete VM from a pre-update snapshot, returning
all services to their previous state at once.
### Version tags
If data remains compatible with the previous version, rollback by downloading the previous release bundle, restoring
your `.env` file, and restarting:
```bash
# Download the previous version's bundle
PREVIOUS="v1.2.3"
curl -L "https://github.com/bbvch-ai/aihub-core/releases/download/${PREVIOUS}/swissaihub-${PREVIOUS}.tar.gz" \
| tar -xz -C /tmp/swissaihub-rollback
# Restore previous compose and configs, keep your .env
cp .env /tmp/swissaihub-rollback/.env
cp -r /tmp/swissaihub-rollback/* .
# Roll back
docker compose pull
docker compose up -d
```
Core and customer code can be rolled back independently if they were updated separately. If both were updated together,
roll back core first, then customer code.
::: warning Services with forward-only migrations cannot be rolled back this way
"If data remains compatible" is the operative condition, and it does not hold for every service. Langfuse applies
forward-only Postgres and ClickHouse migrations on startup, so once an update has run them, downloading the previous
bundle restores the older **binaries** against the newer **schema**. There is no supported downgrade path.
Restoring a pre-update backup is not a rollback either: the restore validates only that the expected artifacts are
present, carries no schema-version stamp, and reverts Postgres and ClickHouse independently with no cross-consistency
check and no post-restore migration step.
Before updating a release that bumps Langfuse, take a backup you are willing to treat as one-way, and plan forward
recovery rather than rollback.
:::
______________________________________________________________________
## Compatibility
Customer code pins to specific core versions to maintain stability. A compatibility matrix tracks which customer
versions work with which core versions:
| Customer Version | Core Version | Status | Notes |
| ---------------- | ------------ | --------- | ------------------ |
| v1.0.0 | v0.1.2 | Legacy | End of life |
| v1.1.0 | v1.2.3 | Supported | Current production |
| v1.2.0 | v1.2.3 | Supported | Latest features |
| v2.0.0 | v2.3.4 | Testing | Next major release |
Staging environments should match production infrastructure and use representative datasets for testing compatibility
before production updates.
______________________________________________________________________
## Monitoring
The observability stack includes Langfuse for AI-specific tracing, OpenTelemetry for distributed tracing, and optional
SigNoz Cloud for external metrics and logs. Monitor core services (API, Web, Dagster) and customer services (agents,
pipelines, processes) during and after updates.
______________________________________________________________________
## Continuous Postgres maintenance
The platform includes automated database maintenance that operators do not need to schedule manually. Two jobs run in
the same Dagster instance as backup (UI at `http://localhost:3004`) and keep the `dagster` database bounded over time:
weekly `dagster_cleanup_job` prunes verbose log entries and transient framework events from `event_logs`, and monthly
`postgres_repack_job` reclaims disk pages via `pg_repack`. Both are mutually exclusive with backup via Dagster's
run-coordinator tag concurrency.
This is what keeps a 5-year-old deployment's Postgres footprint comparable to a 3-month-old one. See
[Backup and Recovery](../4_backup_and_recovery/#continuous-postgres-maintenance) for retention windows and the
`MAINTENANCE_DISABLED` kill switch.
______________________________________________________________________
## Related documentation
- [Deployment Options](../1_deployment_options/) - Per-instance architecture
- [Multi-tenancy](../../16_multi_tenancy/) - Logical separation within instances
- [Backup and Recovery](../4_backup_and_recovery/) - Backup strategies
- [Core Components](../../../1_vision_and_positioning/3_core_components/) - Component dependencies
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/7_network_requirements.md
title: "Network requirements"
description: ""
---
# Network requirements
This page covers network connectivity, firewall rules, and security requirements for production deployments.
## External service connectivity
The Swiss AI Hub VM connects to external services depending on your configuration. All external connections use HTTPS
(port 443).
Which providers you need depends on your deployment configuration.
::: details AI service endpoints
| Service | Endpoint | Port | Purpose |
| --------------- | --------------------------------------------- | ---- | --------------------------------------------------- |
| Swiss LLM Cloud | Configured via `SWISS_LLM_CLOUD_API_BASE_URL` | 443 | Text generation, embedding, reranking, whisper, OCR |
| Hugging Face | `huggingface.co` | 443 | Model downloads for self-hosted inference |
GPU deployments running local vLLM do not require outbound connectivity to LLM providers.
:::
::: details Web search engines (SearXNG meta-search)
The platform's self-hosted SearXNG aggregator queries the search engines below on behalf of Open-WebUI when web search
is enabled. The active engine set is configured in `infra/configs/searxng/settings.yml`; see
[Web search](../8_web_search/) for the engine selection rationale and customisation guide.
| Engine | Endpoint | Port | Purpose |
| ---------- | ------------------- | ---- | ------------------------------------------------- |
| Brave | `search.brave.com` | 443 | General web search (independent index) |
| DuckDuckGo | `duckduckgo.com` | 443 | General web search (no-tracking, proxies Bing) |
| Mojeek | `www.mojeek.com` | 443 | General web search (UK truly-independent crawler) |
| Qwant | `www.qwant.com` | 443 | General web search (French/EU, GDPR-native) |
| Startpage | `www.startpage.com` | 443 | Anonymized Google results (Dutch) |
| Wikidata | `www.wikidata.org` | 443 | Structured-data lookup |
| Wikipedia | `*.wikipedia.org` | 443 | Encyclopedia lookup |
Disable web search in Open-WebUI to drop all of these requirements.
:::
Agents and pipelines can call your existing enterprise systems.
::: details Example customer integration endpoints
| Service | Endpoint | Port | Protocol | Authentication |
| ---------------- | ------------------------- | ---- | --------- | ------------------------------- |
| SharePoint | `.sharepoint.com` | 443 | Graph API | OAuth2 (Azure AD App) |
| Confluence | `.atlassian.net` | 443 | REST | API Token |
| Custom REST APIs | Customer-specific | 443 | REST | Various (API Key, OAuth2, mTLS) |
| SOAP Services | Customer-specific | 443 | SOAP | WS-Security, Basic Auth |
:::
### Identity provider services
User authentication requires connectivity to your configured OIDC provider. The example below shows Microsoft Entra ID
endpoints; substitute with your provider's endpoints as needed.
| Service | Endpoint | Purpose |
| ------------------ | --------------------------- | --------------------------------------------------------------- |
| Microsoft Entra ID | `login.microsoftonline.com` | OAuth2/OIDC user authentication |
| Microsoft Graph | `graph.microsoft.com` | Only needed for SharePoint/OneDrive pipeline sources (not auth) |
### Inbound connections
Users and administrators connect to the Swiss AI Hub on these ports.
| Source | Destination | Port | Purpose |
| -------------- | ------------ | ---- | ------------------------- |
| User Browsers | VM Public IP | 443 | Web UI and chat interface |
| Administrators | VM Public IP | 22 | SSH administrative access |
## Firewall configuration
Production deployments expose three inbound ports. This minimizes the attack surface.
### Inbound rules
Configure these rules in your network security group (NSG) or firewall:
| Priority | Name | Port | Protocol | Purpose |
| -------- | -------------- | ---- | -------- | --------------------------------------------------- |
| 100 | AllowHTTPS | 443 | TCP | Primary access to Swiss AI Hub services |
| 110 | AllowHTTP | 80 | TCP | ACME/Let's Encrypt validation + HTTP→HTTPS redirect |
| 120 | AllowSSH | 22 | TCP | Administrative access (restrict source IPs) |
| 65000 | DenyAllInbound | \* | \* | Default deny all other inbound traffic |
::: tip
Restrict SSH access (port 22) to specific administrator IP addresses or VPN ranges instead of allowing from any source.
:::
### Outbound rules
The Swiss AI Hub needs outbound connectivity for external integrations and updates:
| Priority | Name | Port | Protocol | Purpose |
| -------- | ---------- | ---- | -------- | --------------------------------------------- |
| 100 | AllowHTTPS | 443 | TCP | API calls to LLM providers, external services |
| 110 | AllowHTTP | 80 | TCP | Let's Encrypt certificate validation |
| 120 | AllowDNS | 53 | UDP | DNS resolution |
The platform reaches various external APIs based on your integrations. No additional outbound restrictions are needed.
## Related documentation
- [Deployment options](../1_deployment_options/) - Architecture and hosting strategies
- [Network security](../../20_security/4_network_security/) - Security architecture and defense-in-depth
- [Authentication](../../20_security/1_authentication/) - Identity provider integration details
- [Infrastructure layers](../../../1_vision_and_positioning/4_infrastructure_layers/) - Infrastructure component
overview
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/8_web_search.md
title: "Web search"
description: "How Open-WebUI fetches results from the web through the platform's self-hosted SearXNG instance, and how to customise the engine list."
---
# Web search
Open-WebUI's "web search" feature is wired to a self-hosted [SearXNG](https://docs.searxng.org/) meta-search instance
that ships with the platform. SearXNG forwards each query to a curated set of upstream search engines, merges the
results, and returns them to Open-WebUI without ever logging the user's identity to the upstream providers.
This page explains the engine set, why it was chosen, and how to change it.
## Why SearXNG
SearXNG is the privacy-aware search aggregator behind the platform's web-search feature. Compared to a single hosted
search API, it has three advantages that matter to a self-hosted, data-sovereignty-focused deployment:
- **No vendor lock-in.** Search is not bound to a single provider. Engines can be swapped without code changes.
- **No per-query cost.** SearXNG is open source and self-hosted; there is no metered API key that needs renewal.
- **Auditable egress.** The set of upstream engines is enumerated in configuration, so the outbound network surface is
reviewable and stable.
## Active engine set
The platform ships with seven engines enabled in `infra/configs/searxng/settings.yml`. The list is privacy-first with a
Swiss/European lean, biased toward truly independent indexes:
| Engine | Jurisdiction | Role |
| ---------- | ------------ | ----------------------------------------------------------- |
| Brave | US | Independent crawler/index, no tracking |
| DuckDuckGo | US | Mature no-tracking policy; proxies Bing for breadth |
| Mojeek | UK | Truly-independent crawler (not proxying Google or Bing) |
| Qwant | France/EU | EU-jurisdiction, GDPR-native |
| Startpage | Netherlands | Anonymized Google results (Google quality without tracking) |
| Wikidata | non-profit | Structured knowledge graph |
| Wikipedia | non-profit | Encyclopedic lookup |
The corresponding outbound hostnames are documented in [Network requirements](../7_network_requirements/) so operators
can configure egress firewall rules.
## Engines that are not enabled
A few engines are intentionally absent from the default set, even though SearXNG supports them:
- **Google, Bing.** Direct access to Google and Bing trades meaningful privacy for marginal coverage. Coverage is
preserved indirectly: Startpage proxies Google with tracking stripped, and DuckDuckGo proxies Bing.
- **Yandex, Baidu, 360search.** Russian and Chinese-jurisdiction engines are out of scope for a Swiss/EU-focused
platform and raise compliance and censorship concerns.
- **Specialised category engines** (images, videos, news subengines, code search, file search). Open-WebUI's web-search
feature only consumes general-category results today, so specialised engines would add egress with no benefit.
A few engines we would have liked to include cannot be enabled today:
- **Marginalia** (🇸🇪) — Swedish indie crawler with strong long-form content coverage. Disabled by default upstream and
requires a per-deployment API key (see ).
- **Swisscows** (🇨🇭) — privacy-focused Swiss search; no SearXNG engine module exists.
- **Ecosia** (🇩🇪) — proxies Bing, no native SearXNG module.
- **Kagi** — paid commercial service, no SearXNG module.
## Customising the engine list
The active engines are controlled by the `keep_only` list in `infra/configs/searxng/settings.yml`:
```yaml
use_default_settings:
engines:
keep_only:
- brave
- duckduckgo
- mojeek
- qwant
- startpage
- wikidata
- wikipedia
```
To add an engine, append its name to `keep_only`, regenerate the compose files (`make generate-compose`), and restart
the SearXNG container
(`docker compose -f infra/docker-compose.dev.yml --env-file .env up -d --no-deps --force-recreate searxng`). The full
upstream engine catalogue is in `/usr/local/searxng/searx/settings.yml` inside the container — running
`docker exec searxng grep "name:" /usr/local/searxng/searx/settings.yml` lists everything available.
::: warning
Some engines (Marginalia, several specialised ones) are flagged `disabled: true` upstream and require additional
configuration such as API keys. Adding them to `keep_only` alone will not enable them.
:::
::: tip
Whenever you change the engine list, update the [Network requirements](../7_network_requirements/) page so operators see
the new outbound hostnames.
:::
## Disabling web search entirely
To remove web search from the platform, set `ENABLE_WEB_SEARCH: False` in the Open-WebUI environment block in
`infra/deployment/templates/docker-compose.yml.j2`, regenerate the compose files, and recreate Open-WebUI. The `searxng`
service can then also be removed from the template if no longer needed; this drops all web-search-related outbound
network requirements.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/9_environment_variables.md
title: "Environment Variables"
description: "Reference of every environment variable used by the Swiss AI Hub deployment"
---
# Environment Variables
This page lists every environment variable read by the Swiss AI Hub deployment, organized by role. It is auto-generated from the Pydantic settings classes in `packages/*/swiss_ai_hub/**/*settings.py` and the docker-compose templates. Do not edit by hand — run `make generate-env-docs` to refresh.
Variables fall into three roles:
- **Compose-required** — referenced as `${VAR}` in `docker-compose.yml`. Must be set in `.env` because the project's compose templates do not provide fallback defaults.
- **App-required** — read as a required field by a Pydantic `BaseSettings` class. The application refuses to start if unset.
- **Optional** — has a default in code. Set in `.env` only to override the default.
The deployment has a base variant (`CPU`) plus pure-extension variants (`GPU`). The first section below lists every variable required by the base; subsequent sections list only the extra variables each extension introduces on top of the base.
## Variables for the base deployment (`CPU`)
### Required by docker-compose interpolation
These variables are referenced as `${VAR}` (without a `${VAR:-default}` fallback) somewhere in `docker-compose.yml`. Compose-parse will fail or render empty values if `.env` does not define them. The Consumer column shows the Pydantic settings field that reads the variable when our Python code consumes it; otherwise it points at the config file that embeds it (Keycloak realm import, identity-provider config, etc.) or — if neither — is left empty. The Service(s) column lists the compose service(s) whose `environment:` block receives the variable.
| Variable | Consumer | Service(s) | Description |
|---|---|---|---|
| `ACME_EMAIL` | | `traefik` | |
| `ADMIN_EMAIL` | | `open-webui` | |
| `ADMIN_PASSWORD_HASH` | | `traefik` | |
| `AIHUB_CONFIG_ENCRYPTION_KEY` | `ConfigEncryptionSettings.ENCRYPTION_KEY` | `api`, `document_ingestion_pipeline`, `rclone_pipeline` | Fernet key (url-safe base64, 32 bytes) encrypting secret configuration fields at rest. |
| `AIHUB_CREATE_DEFAULT_BUCKETS` | `AIHubSettings.CREATE_DEFAULT_BUCKETS` | `api`, `seaweedfs-init` | Creates default knowledge buckets and namespaces |
| `AIHUB_DEFAULT_BUCKET_NAME` | `AIHubSettings.DEFAULT_BUCKET_NAME` | `api`, `default_rag_pipeline`, `seaweedfs-init` | Name of the default knowledge bucket |
| `AIHUB_DEFAULT_NAMESPACE_NAME` | `AIHubSettings.DEFAULT_NAMESPACE_NAME` | `api` | Name of the default namespace |
| `AIHUB_SHARED_BUCKET_NAME` | `AIHubSettings.SHARED_BUCKET_NAME` | `api`, `seaweedfs-init`, `shared_rag_pipeline` | Name of the shared knowledge bucket |
| `AIHUB_SHARED_NAMESPACE_NAME` | `AIHubSettings.SHARED_NAMESPACE_NAME` | `api` | Name of the shared namespace |
| `AIHUB_SHOW_LEGACY_KNOWLEDGE` | `AIHubSettings.SHOW_LEGACY_KNOWLEDGE` | `api` | Whether the legacy deploy-bound knowledge databases (default_rag / shared_rag) are listed and readable. Off by default: their pipelines are disabled in every stage, so the databases are obsolete unless a deployment still runs a legacy pipeline — set this to true there to keep managing them. With it off they are neither listed nor browsable, so their documents cannot be reached by name either. Read once at startup: changing it requires an API restart. |
| `AIHUB_STARTUP_TENANT_ACCESS_RULES` | `StartupTenantSettings.ACCESS_RULES` | `api` | Comma-separated access rules for the startup tenant. Use 'aihub.admin.>' for unrestricted access to all platform features. Leave empty to derive the ceiling from the models this instance actually serves, minus AIHUB_TENANT_DEFAULT_ACCESS_EXCLUDED_MODELS — the same default a sysadmin-created tenant gets. Setting it explicitly skips that lookup entirely, so a deployment whose model gateway is not reachable at first boot can still seed its tenant. |
| `AIHUB_STARTUP_TENANT_DESCRIPTION` | `StartupTenantSettings.DESCRIPTION` | `api` | Description of the startup tenant. |
| `AIHUB_STARTUP_TENANT_ID` | `StartupTenantSettings.ID` | `api`, `keycloak` | Unique identifier for the startup tenant. Also used as the Keycloak group name. |
| `AIHUB_STARTUP_TENANT_NAME` | `StartupTenantSettings.NAME` | `api` | Display name of the startup tenant. |
| `AIHUB_TENANT_DEFAULT_ACCESS_AGENT_CLASSES` | `TenantDefaultAccessSettings.AGENT_CLASSES` | `api` | Comma-separated agent classes a new tenant's default ceiling grants, each named as the blueprint reports itself (e.g. ``RAGAgent``). The grant is the blueprint itself, not its existing profiles: the tenant may create assistants of these types, and reaches only the ones it creates. Every other blueprint stays hidden from that tenant until a sysadmin grants it. An allow list rather than exclusions because the discovered-class roster is still empty when the startup tenant is seeded. |
| `AIHUB_TENANT_DEFAULT_ACCESS_EXCLUDED_MODELS` | `TenantDefaultAccessSettings.EXCLUDED_MODELS` | `api` | Comma-separated models withheld from a new tenant's default ceiling, each as ``capability/name`` exactly as LiteLLM reports it (e.g. ``text-generation/Apertus-70B-Instruct-2509``). Matched against the live roster verbatim, so a renamed or removed model silently matches nothing and the capability falls back to a plain wildcard. |
| `AIHUB_USER_SIGNUP_FIRST_ADMIN_USER_ROLES` | `UserSignupSettings.FIRST_ADMIN_USER_ROLES` | `api` | Comma-separated list of roles assigned to the very first user. This user is typically the initial platform administrator. |
| `AIHUB_USER_SIGNUP_REGULAR_USER_ROLES` | `UserSignupSettings.REGULAR_USER_ROLES` | `api` | Comma-separated list of roles assigned to regular users (not the first user). These users typically have standard platform access. |
| `AIHUB_VERSION` | `AIHubSettings.VERSION` | `api`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | Version of the app |
| `AUTH_OPEN_WEBUI_SIGNING_SECRET` | `AuthSettings.OPEN_WEBUI_SIGNING_SECRET` | `api`, `open-webui`, `sysadmin-api` | OpenWebUI signing secret |
| `BACKUP_MINIMUM_KEEP` | `BackupSettings.MINIMUM_KEEP` | `backup-code` | |
| `BACKUP_POSTGRES_SUBPROCESS_TIMEOUT_SECONDS` | `BackupSettings.POSTGRES_SUBPROCESS_TIMEOUT_SECONDS` | `backup-code` | |
| `BACKUP_RETENTION_DAYS` | `BackupSettings.RETENTION_DAYS` | `backup-code` | |
| `BACKUP_S3_BUCKET` | `BackupSettings.S3_BUCKET` | `backup-code` | |
| `DAGSTER_CLEANUP_BATCH_LIMIT` | | `backup-code` | |
| `DAGSTER_DB` | | `backup-code` | |
| `DAGSTER_DEBUG_LOG_RETENTION_DAYS` | | `backup-code` | |
| `DAGSTER_INFO_LOG_RETENTION_DAYS` | | `backup-code` | |
| `DAGSTER_MAX_CONCURRENT_RUNS` | | `dagster-daemon`, `dagster-webserver`, `default_rag_pipeline`, `document_ingestion_pipeline`, `rclone_pipeline`, `shared_rag_pipeline` | |
| `DAGSTER_UNIMPORTANT_EVENT_RETENTION_DAYS` | | `backup-code` | |
| `DAGSTER_WARNING_LOG_RETENTION_DAYS` | | `backup-code` | |
| `DOCUMENT_INGESTION_EMBEDDING_MODEL` | `DocumentIngestionPipelineSettings.EMBEDDING_MODEL` | `document_ingestion_pipeline` | LiteLLM model name used to embed chunks. |
| `DOCUMENT_INGESTION_LLM_MODEL` | `DocumentIngestionPipelineSettings.LLM_MODEL` | `document_ingestion_pipeline` | LiteLLM model name used for summaries and table refinement, and for figure descriptions when no vision model is set. Must support response schemas, which table refinement relies on. |
| `DOCUMENT_INGESTION_OBSERVE_JOB_HOUR` | `DocumentIngestionPipelineSettings.OBSERVE_JOB_HOUR` | `document_ingestion_pipeline` | Hour of the daily per-bucket observation schedule. |
| `DOCUMENT_INGESTION_OBSERVE_JOB_MINUTE` | `DocumentIngestionPipelineSettings.OBSERVE_JOB_MINUTE` | `document_ingestion_pipeline` | Minute of the daily per-bucket observation schedule. |
| `DOCUMENT_INGESTION_VISION_MODEL` | `DocumentIngestionPipelineSettings.VISION_MODEL` | `document_ingestion_pipeline` | LiteLLM model name used for figure descriptions; the text model when unset. Set it whenever the text model cannot read images, or figures are sent to a model that cannot describe them. |
| `DOCUMENT_INGESTION_WITH_FIGURE_DESCRIPTIONS` | `DocumentIngestionPipelineSettings.WITH_FIGURE_DESCRIPTIONS` | `document_ingestion_pipeline` | Generate figure descriptions with a vision LLM. |
| `DOCUMENT_INGESTION_WITH_SUMMARY_NODES` | `DocumentIngestionPipelineSettings.WITH_SUMMARY_NODES` | `document_ingestion_pipeline` | Generate recursive summaries for hierarchical RAG. |
| `DOCUMENT_INGESTION_WITH_TABLE_REFINEMENT` | `DocumentIngestionPipelineSettings.WITH_TABLE_REFINEMENT` | `document_ingestion_pipeline` | Refine tables with the LLM to detect structure and split them. |
| `DOMAIN` | `30-clients.json`, `realm-settings.json` | `api`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `keycloak`, `keycloak-config`, `langfuse-web`, `litellm`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `oauth2proxy-attu`, `oauth2proxy-backup`, `oauth2proxy-dagster`, `oauth2proxy-seaweed`, `open-webui`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `seaweedfs-s3`, `shared_rag_pipeline`, `sysadmin-api`, `sysadmin-web`, `traefik`, `web` | |
| `ENABLE_OTEL_METRICS` | | `open-webui` | |
| `ENV` | | `api`, `open-webui`, `sysadmin-api` | |
| `ETCD_TOKEN` | | `etcd`, `etcd-init`, `milvus-standalone`, `seaweedfs-filer` | |
| `EXPERT_ASKING_CHANNEL_TYPE` | | `expert_asking_agent` | |
| `GEMINI_API_KEY` | | `litellm` | |
| `HUGGINGFACE_API_KEY` | | `litellm`, `vllm`, `vllm-bge-m3`, `vllm-bge-reranker` | |
| `INCIDENT_GITHUB_APP_ID` | `IncidentSettings.GITHUB_APP_ID` | `api` | GitHub App id. The App needs Issues: write and Contents: write. |
| `INCIDENT_GITHUB_INSTALLATION_ID` | `IncidentSettings.GITHUB_INSTALLATION_ID` | `api` | Installation id of the App on the target repository. |
| `INCIDENT_GITHUB_PRIVATE_KEY` | `IncidentSettings.GITHUB_PRIVATE_KEY` | `api` | PEM private key of the GitHub App, used to sign the JWT that buys an installation token. Supply it as a Docker secret mounted at /run/secrets/incident_github_private_key (a compose override adding a `secrets:` entry to the api service) or from a vault, never in a committed env file. An empty INCIDENT_GITHUB_PRIVATE_KEY in the environment does not shadow the secret. |
| `INCIDENT_GITHUB_REPOSITORY` | `IncidentSettings.GITHUB_REPOSITORY` | `api` | Repository issues are filed in, as 'owner/name'. Must be private — reports carry customer data, and a public repository would publish it irreversibly. |
| `KEYCLOAK_ADMIN_PASSWORD` | | `keycloak`, `keycloak-config` | |
| `KEYCLOAK_ADMIN_USER` | | `keycloak`, `keycloak-config` | |
| `KEYCLOAK_API_SERVICE_CLIENT_SECRET` | `KeycloakSettings.API_SERVICE_CLIENT_SECRET` | `api`, `bot`, `keycloak`, `keycloak-config`, `sysadmin-api` | Client secret for the API service account |
| `KEYCLOAK_AZURE_CLIENT_ID` | `identity-providers.json` | `keycloak` | |
| `KEYCLOAK_AZURE_CLIENT_SECRET` | `identity-providers.json` | `keycloak` | |
| `KEYCLOAK_AZURE_TENANT_ID` | `identity-providers.json` | `keycloak` | |
| `KEYCLOAK_OAUTH2_PROXY_ATTU_SECRET` | `30-clients.json` | `keycloak`, `keycloak-config`, `oauth2proxy-attu` | |
| `KEYCLOAK_OAUTH2_PROXY_BACKUP_SECRET` | `30-clients.json` | `keycloak`, `keycloak-config`, `oauth2proxy-backup` | |
| `KEYCLOAK_OAUTH2_PROXY_DAGSTER_SECRET` | `30-clients.json` | `keycloak`, `keycloak-config`, `oauth2proxy-dagster` | |
| `KEYCLOAK_OAUTH2_PROXY_DATALAKE_SECRET` | `30-clients.json` | `keycloak`, `keycloak-config`, `oauth2proxy-seaweed` | |
| `KEYCLOAK_OPENWEBUI_CLIENT_SECRET` | `30-clients.json` | `keycloak`, `keycloak-config`, `open-webui` | |
| `KEYCLOAK_SHOW_KEYCLOAK_LOGIN` | `KeycloakSettings.SHOW_KEYCLOAK_LOGIN` | `api`, `sysadmin-api` | Show a direct Keycloak login button alongside federated IDPs |
| `LANGFUSE_ALLOWED_ORGANIZATION_CREATORS` | | `langfuse-web` | |
| `LANGFUSE_CLICKHOUSE_PASSWORD` | | `backup-code`, `clickhouse`, `langfuse-web`, `langfuse-worker` | |
| `LANGFUSE_ENCRYPTION_KEY` | | `langfuse-web`, `langfuse-worker` | |
| `LANGFUSE_INIT_USER_EMAIL` | | `langfuse-web` | |
| `LANGFUSE_INIT_USER_PASSWORD` | | `langfuse-web` | |
| `LANGFUSE_KEYCLOAK_CLIENT_SECRET` | | `keycloak`, `keycloak-config`, `langfuse-web` | |
| `LANGFUSE_NEXTAUTH_SECRET` | | `langfuse-web` | |
| `LANGFUSE_PUBLIC_KEY` | `LangfuseSettings.PUBLIC_KEY` | `api`, `langfuse-web`, `otel-collector` | Langfuse public API key |
| `LANGFUSE_SALT` | | `langfuse-web`, `langfuse-worker` | |
| `LANGFUSE_SECRET_KEY` | `LangfuseSettings.SECRET_KEY` | `api`, `langfuse-web`, `otel-collector` | Langfuse secret API key |
| `LITELLM_CONFIG_VARIANT` | | `litellm` | |
| `LITELLM_MASTER_KEY` | `litellm-config.yml` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `litellm`, `llm_wrapping_agent`, `memory_writer_agent`, `mineru-api`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | |
| `LITELLM_UI_PASSWORD` | | `litellm` | |
| `LITELLM_UI_USERNAME` | | `litellm` | |
| `LOCAL_LLM_TOKEN` | `litellm-config.yml` | `litellm`, `vllm`, `vllm-bge-m3`, `vllm-bge-reranker` | |
| `LOG_LEVEL` | `LogSettings.LEVEL` | `api`, `bot`, `dagster-daemon`, `dagster-webserver`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `open-webui`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api`, `traefik` | Logging level |
| `MAINTENANCE_DISABLED` | | `backup-code` | |
| `MEM0_EMBEDDING_MODEL_NAME` | `Mem0Settings.EMBEDDING_MODEL_NAME` | `api`, `bot`, `expert_asking_agent`, `expert_rag_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `rag_agent`, `shared_rag_pipeline` | Name of the embedding model to use |
| `MEM0_LLM_NAME` | `Mem0Settings.LLM_NAME` | `api`, `bot`, `expert_asking_agent`, `expert_rag_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `rag_agent`, `shared_rag_pipeline` | Name of the LLM to use. Platform default; an agent may override it per profile. |
| `MEM0_RERANKING_MODEL_NAME` | `Mem0Settings.RERANKING_MODEL_NAME` | `api`, `bot`, `expert_asking_agent`, `expert_rag_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `rag_agent`, `shared_rag_pipeline` | Name of the reranking model to use |
| `MEM0_TELEMETRY` | | `api`, `bot`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | |
| `MILVUS_DIMENSION` | `MilvusSettings.DIMENSION` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | Dimension of the embedding vector |
| `MILVUS_ROOT_PASSWORD` | `MilvusSettings.ROOT_PASSWORD` | `api`, `attu`, `backup-code`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `milvus-standalone`, `namespace_selection_agent`, `open-webui`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | Root password for Milvus authentication. If not set, no auth is used. Username is always 'root'. |
| `MINERU_API_MAX_CONCURRENT_REQUESTS` | | `mineru-api` | |
| `MINERU_API_TIMEOUT` | `MineruSettings.API_TIMEOUT` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | Timeout for MinerU API calls in seconds |
| `MINERU_FORMULA_ENABLE` | `MineruSettings.FORMULA_ENABLE` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | Enable formula/equation parsing |
| `MINERU_MAX_CONCURRENT_BATCH_REQUESTS` | `MineruSettings.MAX_CONCURRENT_BATCH_REQUESTS` | `api`, `default_rag_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | Client-side concurrent page-batch requests per document |
| `MINERU_PAGE_BATCH_SIZE` | `MineruSettings.PAGE_BATCH_SIZE` | `api`, `default_rag_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | Pages per MinerU request for PDFs; keeps server memory constant. 0 disables batching |
| `MINERU_TABLE_ENABLE` | `MineruSettings.TABLE_ENABLE` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | Enable table detection and parsing |
| `MINERU_VLM_NAME` | `MineruSettings.VLM_NAME` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `mineru-api`, `shared_rag_pipeline` | LiteLLM model alias for MinerU VLM |
| `MONGO_PASSWORD` | | `api`, `backup-code`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `ferretdb`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `postgres-ferretdb`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | |
| `MONGO_USERNAME` | | `api`, `backup-code`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `ferretdb`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `postgres-ferretdb`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | |
| `NATS_TOKEN` | `NatsSettings.TOKEN` | `api`, `backup-code`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `nats`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | Authentication token for NATS server. If not set, no auth is used. |
| `NEO4J_PASSWORD` | `Neo4jSettings.PASSWORD` | `api`, `bot`, `expert_asking_agent`, `expert_rag_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `neo4j`, `rag_agent`, `shared_rag_pipeline` | Password for Neo4j DB Server |
| `NEO4J_USERNAME` | `Neo4jSettings.USERNAME` | `api`, `bot`, `expert_asking_agent`, `expert_rag_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `neo4j`, `rag_agent`, `shared_rag_pipeline` | Username for Neo4j DB Server |
| `NOTIFICATION_URLS` | `NotificationSettings.URLS` | `default_rag_pipeline`, `document_ingestion_pipeline`, `rclone_pipeline`, `shared_rag_pipeline` | Apprise notification URIs (comma-separated). Examples: 'slack://TokenA/TokenB/TokenC/#alerts', 'mailto://user:pw@smtp.example.com', 'discord://webhook_id/webhook_token'. See https://github.com/caronc/apprise for the full list. |
| `OAUTH_ADMIN_ROLES_OPENWEBUI` | | `open-webui` | |
| `OAUTH_ALLOWED_GROUPS_ATTU` | | `oauth2proxy-attu` | |
| `OAUTH_ALLOWED_GROUPS_BACKUP` | | `oauth2proxy-backup` | |
| `OAUTH_ALLOWED_GROUPS_DAGSTER` | | `oauth2proxy-dagster` | |
| `OAUTH_ALLOWED_GROUPS_SEAWEEDFS` | | `oauth2proxy-seaweed` | |
| `OAUTH_CLIENT_ID` | | `api`, `bot`, `open-webui`, `sysadmin-api`, `sysadmin-web`, `web` | |
| `OAUTH_CLIENT_SECRET` | | `api`, `bot`, `open-webui`, `sysadmin-api` | |
| `OAUTH_COOKIE_SECRET_ATTU` | | `oauth2proxy-attu` | |
| `OAUTH_COOKIE_SECRET_BACKUP` | | `oauth2proxy-backup` | |
| `OAUTH_COOKIE_SECRET_DAGSTER` | | `oauth2proxy-dagster` | |
| `OAUTH_COOKIE_SECRET_SEAWEEDFS` | | `oauth2proxy-seaweed` | |
| `OAUTH_CUSTOM_SIGN_IN_LOGO` | | `oauth2proxy-attu`, `oauth2proxy-backup`, `oauth2proxy-dagster`, `oauth2proxy-seaweed` | |
| `OAUTH_TENANT_ID` | | `api`, `sysadmin-api` | |
| `OPENWEBUI_BYPASS_ADMIN_ACCESS_CONTROL` | | `open-webui` | |
| `OPENWEBUI_ENABLE_ADMIN_EXPORT` | | `open-webui` | |
| `OPENWEBUI_MODEL_NAME_LOCALE` | `OpenWebuiSettings.MODEL_NAME_LOCALE` | `open-webui` | Locale used to render agent workspace-model names in OpenWebUI, which only stores a single name per model. Falls back to the platform default locale and then any available translation. |
| `OPENWEBUI_SCIM_TOKEN` | `OpenWebuiSettings.SCIM_TOKEN` | `api`, `open-webui`, `sysadmin-api` | SCIM 2.0 bearer token for group and user provisioning |
| `OPENWEBUI_SECRET_KEY` | `OpenWebuiSettings.SECRET_KEY` | `api`, `open-webui`, `sysadmin-api` | OpenWebUI WEBUI_SECRET_KEY for JWT signing |
| `OPENWEBUI_SERVICE_ACCOUNT_ID` | `OpenWebuiSettings.SERVICE_ACCOUNT_ID` | `api`, `openwebui-init`, `sysadmin-api` | UUID of the AI-Hub service account in OpenWebUI's database |
| `OPENWEBUI_WEBHOOK_SECRET` | `OpenWebuiSettings.WEBHOOK_SECRET` | `api`, `open-webui`, `sysadmin-api` | Shared secret for authenticating OpenWebUI webhook calls |
| `OPEN_TERMINAL_API_KEY` | | `open-terminal`, `open-webui` | |
| `OTEL_CLOUD_ENDPOINT` | `otel-config.yml` | `otel-collector` | |
| `OTEL_CLOUD_HEADERS` | `otel-config.yml` | `otel-collector` | |
| `OTEL_ENABLED` | `OpenTelemetrySettings.ENABLED` | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | Enable/disable OpenTelemetry tracing entirely |
| `OTEL_METRICS_ENABLED` | `OpenTelemetrySettings.METRICS_ENABLED` | `api` | Enable/disable OpenTelemetry request metrics (separate from tracing) |
| `POSTGRES_PASSWORD` | `openwebui-init-openwebui.sh` | `backup-code`, `dagster-daemon`, `dagster-webserver`, `default_rag_pipeline`, `document_ingestion_pipeline`, `keycloak`, `langfuse-web`, `langfuse-worker`, `litellm`, `open-webui`, `openwebui-init`, `pgbouncer`, `postgres`, `postgres-ferretdb`, `rclone_pipeline`, `shared_rag_pipeline` | |
| `POSTGRES_PORT` | `openwebui-init-openwebui.sh` | `backup-code`, `openwebui-init` | |
| `POSTGRES_USER` | `openwebui-init-openwebui.sh` | `backup-code`, `dagster-daemon`, `dagster-webserver`, `default_rag_pipeline`, `document_ingestion_pipeline`, `keycloak`, `langfuse-web`, `langfuse-worker`, `litellm`, `open-webui`, `openwebui-init`, `pgbouncer`, `postgres`, `postgres-ferretdb`, `rclone_pipeline`, `shared_rag_pipeline` | |
| `RAG_FILE_MAX_COUNT` | | `open-webui` | |
| `RAG_FILE_MAX_SIZE` | | `open-webui` | |
| `RAG_IMAGE_INLINE_ENABLED` | `RagImageInlineSettings.ENABLED` | `expert_rag_agent`, `litellm`, `rag_agent`, `retrieval_agent` | Whether RAG figures are inlined as base64 at the LiteLLM gateway. |
| `RAG_IMAGE_INLINE_MAX_BYTES` | | `litellm` | |
| `RCLONE_PIPELINE_OBSERVE_JOB_HOUR` | `RclonePipelineSettings.OBSERVE_JOB_HOUR` | `rclone_pipeline` | Hour of the daily per-database sync. Before the ingestion pipeline's midnight observation, so a day's sync is vectorised by the same night's backstop run. |
| `RCLONE_PIPELINE_OBSERVE_JOB_MINUTE` | `RclonePipelineSettings.OBSERVE_JOB_MINUTE` | `rclone_pipeline` | Minute of the daily per-database sync. |
| `RCLONE_RC_PASS` | `RcloneSettings.RC_PASS` | `rclone`, `rclone_pipeline` | RC API password for authentication. |
| `RCLONE_RC_USER` | `RcloneSettings.RC_USER` | `rclone`, `rclone_pipeline` | RC API username for authentication. |
| `REDIS_TOKEN` | `RedisSettings.TOKEN` | `api`, `backup-code`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `langfuse-web`, `langfuse-worker`, `litellm`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `open-webui`, `rag_agent`, `retrieval_agent`, `sysadmin-api`, `valkey` | Authentication token for Redis server. If not set, no auth is used. |
| `S3_STORAGE_ACCESS_KEY` | `S3StorageSettings.ACCESS_KEY` | `api`, `backup-code`, `clickhouse`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_rag_agent`, `imap_agent`, `langfuse-web`, `langfuse-worker`, `milvus-standalone`, `open-webui`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `seaweedfs-init`, `seaweedfs-s3`, `shared_rag_pipeline` | The access key for the s3 endpoint. |
| `S3_STORAGE_SECRET_KEY` | `S3StorageSettings.SECRET_KEY` | `api`, `backup-code`, `clickhouse`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_rag_agent`, `imap_agent`, `langfuse-web`, `langfuse-worker`, `milvus-standalone`, `open-webui`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `seaweedfs-init`, `seaweedfs-s3`, `shared_rag_pipeline` | The secret key for the s3 endpoint. |
| `SCHEDULER_EVENT_RETENTION_DAYS` | `SchedulerSettings.EVENT_RETENTION_DAYS` | `api` | Age beyond which events in a scheduled thread are pruned. Zero disables pruning entirely, which is the default: deleting a tenant's history must be something an operator turned on, never something a deploy started doing. |
| `SCHEDULER_LEASE_TTL_SECONDS` | `SchedulerSettings.LEASE_TTL_SECONDS` | `api` | Leader-lease lifetime. Must exceed the worst-case tick runtime, or the lease expires mid-tick and a second replica starts one concurrently. |
| `SCHEDULER_MAX_CATCHUP_MINUTES` | `SchedulerSettings.MAX_CATCHUP_MINUTES` | `api` | How far back a tick replays after downtime. Occurrences older than this are logged and dropped rather than fired as a burst of stale runs. |
| `SCHEDULER_MAX_RUNS_PER_PROFILE_PER_MONTH` | `SchedulerSettings.MAX_RUNS_PER_PROFILE_PER_MONTH` | `api` | Most runs a single schedule may produce in 30 days, rejected when the profile is saved. Defaults to the tightest schedule cron can express (every minute), so out of the box it rejects nothing expressible and exists for a deployment that wants to allow less. |
| `SCHEDULER_MAX_TOTAL_RUNS_PER_MONTH` | `SchedulerSettings.MAX_TOTAL_RUNS_PER_MONTH` | `api` | Most runs all schedules together may produce in 30 days, checked when a profile is saved. Zero disables the check, which is the default — the right ceiling depends on how many agents a deployment runs and what they cost, and no platform-wide constant knows that. |
| `SCHEDULER_REDIS_KEY_PREFIX` | `SchedulerSettings.REDIS_KEY_PREFIX` | `api` | Namespace for all scheduler keys in Redis. Change it to run an isolated scheduler. |
| `SCHEDULER_RETENTION_INTERVAL_SECONDS` | `SchedulerSettings.RETENTION_INTERVAL_SECONDS` | `api` | Minimum seconds between prunes, enforced cluster-wide. Keeps bulk deletes off the tick. |
| `SCHEDULER_TICK_INTERVAL_SECONDS` | `SchedulerSettings.TICK_INTERVAL_SECONDS` | `api` | Seconds between scheduler ticks. Bounds how late an occurrence can fire. |
| `SEARXNG_SECRET_KEY` | | `searxng` | |
| `SEAWEEDFS_TOKEN` | | `seaweedfs-filer`, `seaweedfs-master`, `seaweedfs-s3`, `seaweedfs-volume` | |
| `SLACK_CHANNEL_ID` | | `expert_asking_agent` | |
| `SLACK_SERVICE_URL` | | `expert_asking_agent` | |
| `STONEY_CLOUD_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `STONEY_CLOUD_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SUPERUSER_EMAIL` | `SuperuserSettings.EMAIL` | `api`, `keycloak`, `sysadmin-api` | Keycloak email used to look up the superuser. |
| `SUPERUSER_FIRSTNAME` | `users-superuser.json` | `keycloak` | |
| `SUPERUSER_LASTNAME` | `users-superuser.json` | `keycloak` | |
| `SUPERUSER_PASSWORD` | `users-superuser.json` | `keycloak` | |
| `SUPERUSER_ROLES_JSON` | `SuperuserSettings.ROLES_JSON` | `api`, `keycloak`, `sysadmin-api` | JSON array of realm roles assigned to the superuser, shared verbatim with the Keycloak realm import via the same environment variable. Must include AIHubSysAdmin. |
| `SUPERUSER_TOKEN` | `SuperuserSettings.TOKEN` | `api`, `open-webui`, `sysadmin-api` | Static bearer token for machine-to-machine API calls. Must start with 'sk-'. |
| `SUPERUSER_USERNAME` | `SuperuserSettings.USERNAME` | `api`, `keycloak`, `sysadmin-api` | Log-only label for the seeded superuser in API startup logs. Not persisted in Keycloak — the Keycloak username is SUPERUSER_EMAIL (registrationEmailAsUsername), so login is by email. |
| `SWISS_LLM_CLOUD_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_EMBEDDING_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_EMBEDDING_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_IMAGE_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_IMAGE_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_OCR_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_OCR_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_RERANKING_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_RERANKING_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_WHISPER_API_BASE_URL` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_WHISPER_API_KEY` | `litellm-config.yml` | `litellm` | |
| `SWISS_LLM_CLOUD_WHISPER_MODEL` | `litellm-config.yml` | `litellm` | |
| `TEAMS_BOT_ID` | | `expert_asking_agent` | |
| `TEAMS_CHANNEL_ID` | | `expert_asking_agent` | |
| `TEAMS_TENANT_ID` | | `expert_asking_agent` | |
### Required by SDK settings classes (not used by the default deployment)
These variables are declared as required fields on a Pydantic `BaseSettings` class and are **not** consumed by any service in the default docker-compose stack. The empty Service(s) column reflects this: no container in the default deployment loads the settings class. They become operationally required only when a custom agent, pipeline, or other extension is added to docker-compose that activates the corresponding SDK functionality (e.g. the SharePoint connector or Azure Document Intelligence loader). Until then, leaving the placeholders unchanged is fine — Pydantic only validates the class when something instantiates it.
| Variable | Consumer | Service(s) | Description |
|---|---|---|---|
| `AZURE_DATA_LAKE_CONNECTION_STRING` | `AzureDataLakeSettings.CONNECTION_STRING` | | Azure Data Lake connection string for explicit authentication. Recommended over implicit authentication. |
| `AZURE_DOCUMENT_INTELLIGENCE_API_KEY` | `AzureDocumentIntelligenceSettings.API_KEY` | | API key for Document Intelligence |
| `AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT` | `AzureDocumentIntelligenceSettings.ENDPOINT` | | |
| `SHAREPOINT_CLIENT_ID` | `SharePointSettings.CLIENT_ID` | | The application (client) ID. |
| `SHAREPOINT_CLIENT_SECRET` | `SharePointSettings.CLIENT_SECRET` | | The client secret for authentication. |
| `SHAREPOINT_SITE_URL` | `SharePointSettings.SITE_URL` | | The SharePoint site URL. |
| `SHAREPOINT_TENANT_ID` | `SharePointSettings.TENANT_ID` | | The Azure AD tenant ID. |
### Optional: override platform defaults
These variables have sensible defaults (or are supplied to containers by docker-compose) and do not need to be set in `.env`. Add them only to override the default. Vars marked `(supplied by compose)` are required by the application but get their value injected through `docker-compose.yml`, so setting them in `.env` has no effect.
| Variable | Consumer | Default | Service(s) | Description |
|---|---|---|---|---|
| `AIHUB_API_DEBUG_MODE` | `AIHubSettings.API_DEBUG_MODE` | `False` | | Debug mode for development |
| `AIHUB_CREATE_DEFAULT_ROLES` | `AIHubSettings.CREATE_DEFAULT_ROLES` | `True` | `api`, `sysadmin-api` | Creates default roles like AI-Hub Admin and AI-Hub User |
| `AIHUB_FRONTEND_ORIGIN` | `AIHubSettings.FRONTEND_ORIGIN` | _(supplied by compose)_ | `api`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | Comma separated list of origins to allow CORS |
| `AIHUB_INTERNAL_API_BASE_URL` | `AIHubSettings.INTERNAL_API_BASE_URL` | `'http://api:8000'` | `sysadmin-api` | Internal base URL of the main platform API (no path), used for server-to-server calls such as the sysadmin plane proxying the access-capability catalog. Predictable per deployment: the Docker service name in compose, localhost in local dev. |
| `AIHUB_MONGO_MAIN_DB_NAME` | `AIHubSettings.MONGO_MAIN_DB_NAME` | `'aihub'` | | Name of mongodb database that will be used to store data |
| `AIHUB_OPENAI_API_BASE_URL` | `AIHubSettings.OPENAI_API_BASE_URL` | `'http://api:8000/api/v1/active/openai'` | `api` | Base URL of AI-Hub's OpenAI-compatible endpoint, used for Langfuse LLM connection |
| `AUTH_ENABLE_API_ACCESS` | `AuthSettings.ENABLE_API_ACCESS` | `True` | `api`, `sysadmin-api` | Enable API access |
| `AZURE_DOCUMENT_INTELLIGENCE_API_VERSION` | `AzureDocumentIntelligenceSettings.API_VERSION` | `'2024-11-30'` | | |
| `AZURE_DOCUMENT_INTELLIGENCE_EXTENSIONS` | `AzureDocumentIntelligenceSettings.EXTENSIONS` | `['pdf', 'docx', 'xlsx', 'pptx', 'html']` | | Supported file extensions for document processing |
| `BACKUP_AWS_ENDPOINT_URL` | `BackupSettings.AWS_ENDPOINT_URL` | `'http://seaweedfs-s3:9000'` | `backup-code` | |
| `BACKUP_CLICKHOUSE_HOST` | `BackupSettings.CLICKHOUSE_HOST` | `'clickhouse'` | `backup-code` | |
| `BACKUP_CLICKHOUSE_PORT` | `BackupSettings.CLICKHOUSE_PORT` | `8123` | | |
| `BACKUP_CLICKHOUSE_USER` | `BackupSettings.CLICKHOUSE_USER` | `'clickhouse'` | `backup-code` | |
| `BACKUP_DAGSTER_CLEANUP_BATCH_LIMIT` | `BackupSettings.DAGSTER_CLEANUP_BATCH_LIMIT` | `1000000` | `backup-code` | |
| `BACKUP_DAGSTER_DB` | `BackupSettings.DAGSTER_DB` | `'dagster'` | `backup-code` | |
| `BACKUP_DAGSTER_DEBUG_LOG_RETENTION_DAYS` | `BackupSettings.DAGSTER_DEBUG_LOG_RETENTION_DAYS` | `7` | `backup-code` | |
| `BACKUP_DAGSTER_INFO_LOG_RETENTION_DAYS` | `BackupSettings.DAGSTER_INFO_LOG_RETENTION_DAYS` | `60` | `backup-code` | |
| `BACKUP_DAGSTER_UNIMPORTANT_EVENT_RETENTION_DAYS` | `BackupSettings.DAGSTER_UNIMPORTANT_EVENT_RETENTION_DAYS` | `30` | `backup-code` | |
| `BACKUP_DAGSTER_WARNING_LOG_RETENTION_DAYS` | `BackupSettings.DAGSTER_WARNING_LOG_RETENTION_DAYS` | `60` | `backup-code` | |
| `BACKUP_LANGFUSE_CLICKHOUSE_PASSWORD` | `BackupSettings.LANGFUSE_CLICKHOUSE_PASSWORD` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_MAINTENANCE_DISABLED` | `BackupSettings.MAINTENANCE_DISABLED` | `False` | `backup-code` | |
| `BACKUP_MILVUS_HOST` | `BackupSettings.MILVUS_HOST` | `'milvus-standalone'` | `backup-code` | |
| `BACKUP_MILVUS_PORT` | `BackupSettings.MILVUS_PORT` | `19530` | `backup-code` | |
| `BACKUP_MILVUS_ROOT_PASSWORD` | `BackupSettings.MILVUS_ROOT_PASSWORD` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_MONGO_PASSWORD` | `BackupSettings.MONGO_PASSWORD` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_MONGO_USERNAME` | `BackupSettings.MONGO_USERNAME` | `'admin'` | `backup-code` | |
| `BACKUP_NATS_TOKEN` | `BackupSettings.NATS_TOKEN` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_NATS_URL` | `BackupSettings.NATS_URL` | `'nats://nats:4222'` | `backup-code` | |
| `BACKUP_NEO4J_CONTAINER` | `BackupSettings.NEO4J_CONTAINER` | `'neo4j'` | `backup-code` | |
| `BACKUP_POSTGRES_FERRETDB_HOST` | `BackupSettings.POSTGRES_FERRETDB_HOST` | `'postgres-ferretdb'` | `backup-code` | |
| `BACKUP_POSTGRES_HOST` | `BackupSettings.POSTGRES_HOST` | `'postgres'` | `backup-code` | |
| `BACKUP_POSTGRES_PASSWORD` | `BackupSettings.POSTGRES_PASSWORD` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_POSTGRES_PORT` | `BackupSettings.POSTGRES_PORT` | `5432` | `backup-code` | |
| `BACKUP_POSTGRES_USER` | `BackupSettings.POSTGRES_USER` | `'admin'` | `backup-code` | |
| `BACKUP_REDIS_TOKEN` | `BackupSettings.REDIS_TOKEN` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_S3_STORAGE_ACCESS_KEY` | `BackupSettings.S3_STORAGE_ACCESS_KEY` | `'admin'` | `backup-code` | |
| `BACKUP_S3_STORAGE_SECRET_KEY` | `BackupSettings.S3_STORAGE_SECRET_KEY` | _(supplied by compose)_ | `backup-code` | |
| `BACKUP_VALKEY_CONTAINER` | `BackupSettings.VALKEY_CONTAINER` | `'valkey'` | `backup-code` | |
| `BACKUP_VALKEY_HOST` | `BackupSettings.VALKEY_HOST` | `'valkey'` | | |
| `BACKUP_VALKEY_PORT` | `BackupSettings.VALKEY_PORT` | `6379` | | |
| `INCIDENT_MAX_ATTACHMENTS` | `IncidentSettings.MAX_ATTACHMENTS` | `5` | | How many files one report may carry. Attachments are committed to the repository and cannot meaningfully be removed from its history afterwards, which is why this is deliberately small. |
| `INCIDENT_MAX_ATTACHMENT_BYTES` | `IncidentSettings.MAX_ATTACHMENT_BYTES` | `5242880` | | Largest single attachment accepted, in bytes. |
| `KEYCLOAK_API_SERVICE_CLIENT_ID` | `KeycloakSettings.API_SERVICE_CLIENT_ID` | `'aihub-api-service'` | | Client ID for the API service account |
| `KEYCLOAK_EXTERNAL_URL` | `KeycloakSettings.EXTERNAL_URL` | `None` | `api`, `bot`, `sysadmin-api` | Keycloak external URL as seen by browsers, used for issuer validation |
| `KEYCLOAK_REALM` | `KeycloakSettings.REALM` | `'aihub'` | `api`, `bot`, `sysadmin-api` | Keycloak realm name |
| `KEYCLOAK_URL` | `KeycloakSettings.URL` | _(supplied by compose)_ | `api`, `bot`, `keycloak-config`, `sysadmin-api` | Keycloak internal URL for direct access (e.g., http://keycloak:8080) |
| `LANGFUSE_BASE_URL` | `LangfuseSettings.BASE_URL` | _(supplied by compose)_ | `api` | Langfuse server base URL |
| `LANGFUSE_PROJECT_ID` | `LangfuseSettings.PROJECT_ID` | `None` | `api` | Langfuse project ID for constructing dataset URLs |
| `LANGFUSE_PUBLIC_URL` | `LangfuseSettings.PUBLIC_URL` | `None` | `api` | Public-facing Langfuse URL for browser links (e.g. https://langfuse.example.com) |
| `LANGFUSE_TIMEOUT` | `LangfuseSettings.TIMEOUT` | `60` | | Timeout in seconds for Langfuse API requests |
| `LITE_LLM_PROXY_API_KEY` | `LiteLLMProxySettings.API_KEY` | `None` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | API key for authentication. If not provided, other authentication methods will be used. |
| `LITE_LLM_PROXY_BASE_URL` | `LiteLLMProxySettings.BASE_URL` | _(supplied by compose)_ | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | The base URL of the model. |
| `LITE_LLM_PROXY_INTERNAL_BASE_URL` | `LiteLLMProxySettings.INTERNAL_BASE_URL` | `None` | | In-cluster LiteLLM URL that Langfuse dials from its own containers when running managed evaluators. Falls back to BASE_URL, which is already correct wherever the API and Langfuse share a network; set it only where they do not, as in local development where the API runs on the host. Its hostname must appear in LANGFUSE_LLM_CONNECTION_WHITELISTED_HOST on langfuse-web and langfuse-worker. |
| `LITE_LLM_PROXY_USER_BUDGET_DURATION` | `LiteLLMProxySettings.USER_BUDGET_DURATION` | `None` | | Budget is reset at the end of specified duration. If not set, budget is never reset. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"), months ("1mo"). |
| `LITE_LLM_PROXY_USER_MAX_BUDGET` | `LiteLLMProxySettings.USER_MAX_BUDGET` | `None` | | Budget available to a user in one period |
| `LITE_LLM_PROXY_USER_MAX_PARALLEL_REQUESTS` | `LiteLLMProxySettings.USER_MAX_PARALLEL_REQUESTS` | `None` | | Rate limit a user based on the number of parallel requests. Raises 429 error, if user's parallel requests > x. |
| `LITE_LLM_PROXY_USER_RPM_LIMIT` | `LiteLLMProxySettings.USER_RPM_LIMIT` | `None` | | Specify rpm limit for a given user (Requests per minute) |
| `LITE_LLM_PROXY_USER_SOFT_BUDGET` | `LiteLLMProxySettings.USER_SOFT_BUDGET` | `None` | | Get alerts when user crosses given budget, doesn't block requests. |
| `LITE_LLM_PROXY_USER_TPM_LIMIT` | `LiteLLMProxySettings.USER_TPM_LIMIT` | `None` | | Specify tpm limit for a given user (Tokens per minute) |
| `MEM0_SEARCH_QUERY_EMBEDDING_WINDOW` | `Mem0Settings.SEARCH_QUERY_EMBEDDING_WINDOW` | `None` | | Override for the embedding model's input window, in the model's own tokens. Set it only when LiteLLM reports no or an incorrect max_input_tokens; None resolves it at first use. Queries are truncated to half this value: they can only be counted with tiktoken, which undercounts the embedder's tokenizer by up to 2x. |
| `MEM0_SUPPORT_VISION` | `Mem0Settings.SUPPORT_VISION` | `True` | | Whether to support vision |
| `MEM0_VISION_DETAIL` | `Mem0Settings.VISION_DETAIL` | `'auto'` | | Vision details |
| `MEMORY_DEFAULT_TENANT_ID` | `MemorySettings.DEFAULT_TENANT_ID` | `'AIHub'` | | Default tenant ID for memory scoping |
| `MEMORY_DEFAULT_TENANT_NAMESPACE` | `MemorySettings.DEFAULT_TENANT_NAMESPACE` | `None` | | Default tenant namespace for department-level scoping |
| `MILVUS_URL` | `MilvusSettings.URL` | _(supplied by compose)_ | `api`, `attu`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `shared_rag_pipeline` | Connection URL for Milvus DB Server |
| `MINERU_API_BASE_URL` | `MineruSettings.API_BASE_URL` | `'http://mineru-api:8000'` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | MinerU API endpoint URL |
| `MINERU_EXTENSIONS` | `MineruSettings.EXTENSIONS` | `['pdf', 'png', 'jpeg', 'jp2', 'webp', 'gif', 'bmp', 'jpg', 'tiff']` | | File extensions supported by MinerU |
| `MINERU_VLM_SERVER_API_KEY` | `MineruSettings.VLM_SERVER_API_KEY` | `SecretStr('')` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | LiteLLM API key for VLM requests |
| `MINERU_VLM_SERVER_URL` | `MineruSettings.VLM_SERVER_URL` | `'http://litellm:4000'` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `shared_rag_pipeline` | LiteLLM proxy URL for VLM routing |
| `MONGO_CONNECTION_STRING` | `MongoSettings.CONNECTION_STRING` | _(supplied by compose)_ | `api`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | Overwrite the MongoDB connection string |
| `NATS_ENDPOINT` | `NatsSettings.ENDPOINT` | _(supplied by compose)_ | `api`, `bot`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline`, `sysadmin-api` | Connection endpoint for NATS messaging system |
| `NEO4J_URL` | `Neo4jSettings.URL` | _(supplied by compose)_ | `api`, `bot`, `expert_asking_agent`, `expert_rag_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `rag_agent`, `shared_rag_pipeline` | Connection URL for Neo4j DB Server |
| `NOTIFICATION_DAGSTER_UI_BASE_URL` | `NotificationSettings.DAGSTER_UI_BASE_URL` | `None` | `default_rag_pipeline`, `document_ingestion_pipeline`, `rclone_pipeline`, `shared_rag_pipeline` | Base URL of the Dagster UI used to build deep links in notification bodies (e.g. 'https://dagster.example.com'). |
| `NOTIFICATION_MIN_INTERVAL_SECONDS` | `NotificationSettings.MIN_INTERVAL_SECONDS` | `30` | `default_rag_pipeline`, `document_ingestion_pipeline`, `rclone_pipeline`, `shared_rag_pipeline` | Minimum interval between sensor ticks in seconds. |
| `NOTIFICATION_TITLE_PREFIX` | `NotificationSettings.TITLE_PREFIX` | `'Swiss AI Hub Pipeline'` | `default_rag_pipeline`, `document_ingestion_pipeline`, `rclone_pipeline`, `shared_rag_pipeline` | Prefix prepended to the notification title. |
| `OPENWEBUI_BASE_URL` | `OpenWebuiSettings.BASE_URL` | _(supplied by compose)_ | `api`, `sysadmin-api` | OpenWebUI server base URL |
| `OPENWEBUI_CONVERSATION_METADATA_MODEL` | `OpenWebuiSettings.CONVERSATION_METADATA_MODEL` | `'text-generation/gemma-4-31B-it'` | `api` | LiteLLM name (``capability/name``) of the model generating conversation metadata — chat titles and follow-up questions — for chats that have no agent behind them. OpenWebUI runs it under the end user's identity, so a role without access to this model loses both features silently; the access catalog flags the model for that reason. Same model as the OpenWebUI ``TASK_MODEL`` env var but in LiteLLM form, since that one names the workspace model wrapping it. |
| `OTEL_BLRP_MAX_EXPORT_BATCH_SIZE` | `OpenTelemetrySettings.BLRP_MAX_EXPORT_BATCH_SIZE` | `2048` | | Log records per OTLP export |
| `OTEL_BLRP_MAX_QUEUE_SIZE` | `OpenTelemetrySettings.BLRP_MAX_QUEUE_SIZE` | `16384` | | Log records buffered before new ones are dropped |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `OpenTelemetrySettings.EXPORTER_OTLP_ENDPOINT` | `None` | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `litellm`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `open-webui`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | OTLP exporter endpoint URL |
| `OTEL_EXPORTER_OTLP_INSECURE` | `OpenTelemetrySettings.EXPORTER_OTLP_INSECURE` | `True` | `open-webui` | Use insecure connection (no TLS) for gRPC |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `OpenTelemetrySettings.EXPORTER_OTLP_PROTOCOL` | `'grpc'` | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `litellm`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `open-webui`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | OTLP protocol |
| `OTEL_EXPORTER_OTLP_TIMEOUT` | `OpenTelemetrySettings.EXPORTER_OTLP_TIMEOUT` | `60` | | Seconds an OTLP export may spend retrying |
| `OTEL_RESOURCE_SERVICE_NAME` | `OpenTelemetrySettings.RESOURCE_SERVICE_NAME` | `None` | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | Resource service name |
| `OTEL_RESOURCE_SERVICE_NAMESPACE` | `OpenTelemetrySettings.RESOURCE_SERVICE_NAMESPACE` | `None` | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | Resource service namespace |
| `OTEL_RESOURCE_SERVICE_VERSION` | `OpenTelemetrySettings.RESOURCE_SERVICE_VERSION` | `None` | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | Resource service version |
| `PARSING_PASSTHROUGH_EXTENSIONS` | `ParsingSettings.PASSTHROUGH_EXTENSIONS` | `[]` | | File extensions that return empty content instead of 400 (e.g. for agent-only processing). Set via env as JSON array: PARSING_PASSTHROUGH_EXTENSIONS='["zip","wav"]' |
| `RCLONE_LOCAL_SOURCE_ROOT` | `RcloneSettings.LOCAL_SOURCE_ROOT` | `None` | | Directory inside the rclone container that knowledge databases with a `local` source may read. Unset (the default) removes the `local` backend from the offered sources, because the daemon's own filesystem would otherwise be readable by every knowledge administrator. |
| `RCLONE_PIPELINE_MAX_PARTITIONS` | `RclonePipelineSettings.MAX_PARTITIONS` | `1000` | | Maximum partitions added or removed per database per observation. |
| `RCLONE_URL` | `RcloneSettings.URL` | `'http://rclone:5572'` | `rclone_pipeline` | Rclone RC API URL (e.g., http://rclone:5572). |
| `REDIS_URL` | `RedisSettings.URL` | _(supplied by compose)_ | `api`, `email_classification_agent`, `expert_asking_agent`, `expert_rag_agent`, `few_shot_agent`, `imap_agent`, `llm_wrapping_agent`, `memory_writer_agent`, `namespace_selection_agent`, `open-webui`, `rag_agent`, `retrieval_agent`, `sysadmin-api` | Connection URL for Redis server (without token) |
| `S3_STORAGE_ENDPOINT` | `S3StorageSettings.ENDPOINT` | _(supplied by compose)_ | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_rag_agent`, `imap_agent`, `litellm`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline` | The s3 endpoint from either aws or minio. |
| `S3_STORAGE_INTERNAL_ENDPOINT` | `S3StorageSettings.INTERNAL_ENDPOINT` | `None` | | The in-cluster s3 endpoint (Docker DNS) for presigned URLs that are fetched by other in-cluster services (e.g. the LiteLLM gateway inlining RAG figures). Only ever signed against, never connected to, so it works from the host without DNS resolution. If not set, falls back to ENDPOINT. |
| `S3_STORAGE_PUBLIC_ENDPOINT` | `S3StorageSettings.PUBLIC_ENDPOINT` | `None` | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_rag_agent`, `imap_agent`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline` | The publicly accessible s3 endpoint for presigned URLs. If not set, falls back to ENDPOINT. Use this when internal and external endpoints differ (e.g., Docker internal vs Traefik-routed external). |
| `S3_STORAGE_REGION` | `S3StorageSettings.REGION` | `'us-east-1'` | | The region for the s3 endpoint. For minio, value does not matter |
| `S3_STORAGE_URL_SIGNING_SECRET` | `S3StorageSettings.URL_SIGNING_SECRET` | _(supplied by compose)_ | `api`, `default_rag_pipeline`, `document_ingestion_pipeline`, `email_classification_agent`, `expert_rag_agent`, `imap_agent`, `rag_agent`, `rclone_pipeline`, `retrieval_agent`, `shared_rag_pipeline` | A secret key used for signing and verifying temporary anonymous access URLs. |
| `USAGE_LIMIT_WARNING_THRESHOLD_PERCENT` | `UsageLimitSettings.WARNING_THRESHOLD_PERCENT` | `80` | | Usage percentage at which warning headers are emitted |
## Additional variables for `GPU` deployments
The `GPU` variant currently does not introduce any operator-controlled variables beyond the base set above.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/10_identity_provider_setup.md
title: "Identity Provider Setup"
description: "Connect an external identity provider to the Swiss AI Hub through Keycloak"
---
# Identity Provider Setup
The Swiss AI Hub does not manage user credentials itself. It uses **Keycloak as an identity broker** that federates to
your organization's identity provider (IdP). Users sign in with their existing corporate account; Keycloak validates the
login and issues the platform a token.
Keycloak can broker providers over three protocols, plus a range of built-in social providers — see the
[Keycloak identity brokering documentation](https://www.keycloak.org/docs/latest/server_admin/index.html#_identity_broker)
for the full list:
- [OpenID Connect v1.0](https://www.keycloak.org/docs/latest/server_admin/index.html#_identity_broker_oidc)
- [OAuth v2](https://www.keycloak.org/docs/latest/server_admin/index.html#_identity_broker_oauth)
- [SAML v2.0](https://www.keycloak.org/docs/latest/server_admin/index.html#saml-v2-0-identity-providers)
The platform places no restriction on the protocol — any enabled, visible provider configured in the `aihub` realm
appears on the login page. The one practical requirement for **role-based access** is that the provider emits the AI-Hub
role values in a claim that Keycloak maps to realm roles (see
[User and Role Management](./1_azure_entra_id/2_user_and_role_management/)).
The pages below walk through the IdPs we configure and support out of the box. Each one explains how to set up the
provider so it matches what the Keycloak `aihub` realm expects.
## Supported providers
- **[Microsoft Entra ID (Azure AD)](./1_azure_entra_id/)** — create the app registration and manage its users and roles.
::: tip
This section is operational. For the conceptual model — how Keycloak validates tokens, maps claims, and enforces roles —
see [Authentication and Authorization](../../20_security/1_authentication/).
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/10_identity_provider_setup/1_azure_entra_id.md
title: "Microsoft Entra ID"
description: "Federate the Swiss AI Hub to Microsoft Entra ID (Azure AD) via Keycloak"
---
# Microsoft Entra ID
Keycloak's `aihub` realm ships with a Microsoft Entra ID provider (alias `azure-ad`) already defined. To activate it,
create an **App Registration** in your Entra tenant configured as the realm expects, define its app roles, and assign
users.
Entra ID is integrated as an
[OpenID Connect v1.0 identity provider](https://www.keycloak.org/docs/latest/server_admin/index.html#_identity_broker_oidc).
Keycloak validates the ID token against Entra's JWKS endpoint and authenticates with a client secret plus PKCE (`S256`)
— there is nothing to choose here, the realm is preconfigured for OIDC.
## Pages
- **[Azure App Registration](./1_azure_app_registration/)** — create the app registration and configure it exactly as
Keycloak expects (redirect URI, scopes, client secret).
- **[User and Role Management](./2_user_and_role_management/)** — define the AI-Hub app roles and assign them to users
so they can log in and gain the right access.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/10_identity_provider_setup/1_azure_entra_id/1_azure_app_registration.md
title: "Azure App Registration"
description: "Configure an Entra ID app registration so Keycloak accepts it as an identity provider"
---
# Azure App Registration
Keycloak's `aihub` realm ships with a Microsoft Entra ID provider (alias `azure-ad`) already defined. To activate it,
create an **App Registration** in your Entra tenant configured as described below, then hand three values to the
platform.
::: info Prerequisites
An Entra ID tenant and permission to create app registrations and assign enterprise application roles. Creating an app
registration itself is standard Entra administration — see
[Microsoft's documentation](https://learn.microsoft.com/entra/identity-platform/quickstart-register-app). This page
documents only the AI-Hub-specific configuration.
:::
## What the platform needs from Azure
After configuring the app registration, set these three variables in your `.env` file. Keycloak reads them at startup
and injects them into the `azure-ad` provider:
| Variable | Source in Azure |
| ------------------------------ | --------------------------------------- |
| `KEYCLOAK_AZURE_CLIENT_ID` | Application (client) ID of the app reg. |
| `KEYCLOAK_AZURE_TENANT_ID` | Directory (tenant) ID |
| `KEYCLOAK_AZURE_CLIENT_SECRET` | Value of a client secret you create |
## Required configuration
### Redirect URI
Register this exact **Web** redirect URI (it is Keycloak's broker endpoint for the `azure-ad` alias on the `aihub`
realm):
```text
https://auth./realms/aihub/broker/azure-ad/endpoint
```
Replace `` with your deployment domain. For local development, also add:
```text
http://localhost:8180/realms/aihub/broker/azure-ad/endpoint
```
### API permissions
Keycloak requests the standard OpenID Connect scopes — no Microsoft Graph permissions are required:
```text
openid email profile
```
These provide the claims Keycloak maps to the user: `email`, `given_name`, `family_name`, and `preferred_username`.
### Client secret
Create a client secret and copy its **value** (not the secret ID) into `KEYCLOAK_AZURE_CLIENT_SECRET`. Keycloak
authenticates to Azure with this secret (`client_secret_post`) and adds PKCE (`S256`) on top.
::: warning
Client secrets expire. Set a calendar reminder before the expiry date and rotate the secret by updating
`KEYCLOAK_AZURE_CLIENT_SECRET` — an expired secret breaks all logins through this provider.
:::
## Next step
The app registration is not usable until you define and assign its **app roles** — see
[User and Role Management](../2_user_and_role_management/). At minimum, users need the `AIHubAccess` role to log in.
::: tip Operators don't edit the provider
The `azure-ad` provider and its claim mappers are defined in
`infra/deployment/templates/configs/keycloak/bootstrap/identity-providers.json.j2`. You normally only set the three
`.env` variables — no Keycloak configuration is needed. This is bootstrap config: it is applied by the realm import on
the **first start only**, so manual admin-console edits to the provider survive restarts, and changes to the file reach
an already-initialized deployment only via the admin console (or a fresh realm database).
:::
::: tip Multi-tenant deployments
A single deployment can federate multiple organizations, each with its own app registration mapped to a separate tenant
group in Keycloak. This is an advanced, non-default setup; see the comments in `bootstrap/identity-providers.json.j2`
for the hardcoded-group mapper pattern.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/3_deployment_guide/10_identity_provider_setup/1_azure_entra_id/2_user_and_role_management.md
title: "User and Role Management"
description: "Define and assign the app roles that grant access to the Swiss AI Hub"
---
# User and Role Management
Access to the Swiss AI Hub is granted through **app roles** on the Azure app registration. When a user logs in, Entra ID
includes their assigned app roles in the `roles` claim; Keycloak maps each one to a realm role of the same name.
Two roles take effect in the platform — define both on the app registration and assign them to users.
## Define the app roles
In the app registration, add these app roles (App roles blade, or the manifest). The **value** must match exactly — it
is what Keycloak maps:
| Display name | Value | Grants |
| --------------- | --------------- | ------------------------------------------------------------------ |
| AI-Hub Access | `AIHubAccess` | Permission to log in. **Required** — without it, login is denied. |
| AI-Hub Sysadmin | `AIHubSysAdmin` | Platform administrator + access to admin tools (Dagster, Attu, …). |
Equivalent `appRoles` manifest entries:
```json
"appRoles": [
{
"displayName": "AI-Hub Access",
"value": "AIHubAccess",
"description": "Allows login to the Swiss AI Hub",
"allowedMemberTypes": ["User"],
"isEnabled": true
},
{
"displayName": "AI-Hub Sysadmin",
"value": "AIHubSysAdmin",
"description": "Platform administrator and admin tool access",
"allowedMemberTypes": ["User"],
"isEnabled": true
}
]
```
::: warning
`AIHubAccess` is mandatory. Keycloak's login flow denies any user who does not have it, regardless of other roles. Every
user who should reach the platform must be assigned `AIHubAccess`.
:::
## Assign roles to users
Assign the app roles to users or groups on the backing **Enterprise Application** — see Microsoft's
[Assign users and groups to an application](https://learn.microsoft.com/entra/identity/enterprise-apps/assign-user-or-group-access-portal).
Assigned app roles appear automatically in the token's `roles` claim; no optional-claims configuration is needed.
A typical assignment:
- **All platform users** → `AIHubAccess`
- **Platform administrators** → `AIHubAccess` **and** `AIHubSysAdmin`
::: tip
Assigning a role to a **group** (rather than individual users) requires a Microsoft Entra ID P1 or P2 license and
`"Group"` in the role's `allowedMemberTypes`. Per-user assignment works on any tier.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/4_user_interface.md
title: "User Interface"
description: ""
---
# The Platform interface
The Swiss AI Hub interface is designed as an integrated suite, not a collection of separate tools. This approach mirrors
familiar productivity software like Microsoft Office or Google Workspace, where different applications for different
tasks coexist within a single, unified environment.
Users authenticate once to access a cohesive suite of AI services. A shared navigation framework and a consistent design
language across all capabilities reduce the learning curve and eliminate the workflow disruption common in fragmented
platforms. This design allows users to focus on their work, not on learning how to navigate different tools.
## The service catalog
The interface provides a set of specialized services that cover the entire lifecycle of enterprise AI, from knowledge
management and agent development to process automation and evaluation.
### Agent management
This is the central hub for discovering, interacting with, and monitoring all AI agents deployed in your organization.
It provides a browsable catalog of available agents, allowing users to understand their capabilities before engaging
with them.
::: details Key capabilities
- **Agent discovery**: Browse a visual catalog of all agents you are authorized to access, with descriptions and status
indicators.
- **Workflow visualization**: Examine agent workflows as interactive diagrams showing their decision logic and tool
integrations.
- **Direct interaction**: Initiate chat sessions directly from the agent's profile.
- **Thread overview**: View all conversation threads associated with a specific agent to review its interaction history.
- **Status monitoring**: See real-time indicators showing whether agents are running, stopped, or experiencing errors.
:::
### Thread management
This service provides a complete history of all conversations between users and AI agents. It allows you to resume past
interactions, review an agent's reasoning, and maintain a full audit trail of AI-powered dialogues.
::: details Key capabilities
- **Thread catalog**: View, search, and filter all your past conversation threads.
- **Conversation history**: Access the complete message history for any thread, with timestamps and participant details.
- **Resume conversations**: Continue any past interaction from where you left off, with full context preserved.
- **Display events**: See a rich visualization of an agent's internal operations during a conversation, including its
thought process, tool use, and data retrieval steps.
:::
### Knowledge management
This service gives you transparent control over the knowledge bases that your AI agents use for Retrieval-Augmented
Generation (RAG). You can manage the documents and data that provide context for your agents' responses.
::: details Key capabilities
- **Organize knowledge**: Structure information in databases and namespaces that mirror your organization's data
structures.
- **Document upload**: Manually upload documents with previews and validation.
- **Processing transparency**: See exactly how documents are parsed, chunked, and prepared for AI retrieval.
- **Document reconstruction**: View the final, processed version of a document to understand how an agent sees it.
:::
### My Files
Every user has a personal file space: the files they attached in agent chats, the files agents made for them, and their
own uploads. It is the user's home in the code sandbox, so what an agent writes there and what the user sees here is the
same thing. The service is available on its own page and beside a chat in OpenWebUI. See
[My Files](../10_chat_ui/14_my_files/) for the user-facing behavior.
::: details Key capabilities
- **Browse and preview**: Folders per conversation, titled with the chat, with previews of text, images and PDFs.
- **Manage**: Upload, download, rename, move into folders and delete.
- **Private by construction**: Each user sees only their own files, and every path is confined to their home.
:::
### Process management
This service is for visualizing and managing complex, multi-step workflows that involve AI agents, human decision
points, and integrations with external systems. It provides operational visibility into sophisticated AI-powered
automation.
::: details Key capabilities
- **Process visualization**: View interactive diagrams of your automated business processes.
- **Execution monitoring**: Track the real-time progress of running processes, seeing which step is currently active.
- **Human intervention**: Participate in workflows that require human approval or review at designated steps.
- **Execution history**: Review a complete audit trail of every process run, with step-by-step logs and outcomes.
:::
### Evaluation service
This service brings systematic testing and quality assurance to your AI agents. It allows you to validate agent
performance against predefined datasets to ensure quality and accuracy before and after deployment.
::: details Key capabilities
- **Dataset management**: Upload and manage test datasets with question-answer pairs or other evaluation criteria.
- **Experiment configuration**: Define and run automated experiments that test agents against your datasets.
- **Results analysis**: View comprehensive results showing success rates, performance metrics, and failure analysis.
- **Comparative analysis**: Compare the results of different experiments to measure the impact of configuration changes.
:::
### Administrative services
A set of services enables administrators to manage users, roles, and permissions through an intuitive interface. This
centralizes platform governance for security, compliance, and operational teams.
::: details Key capabilities
- **User management**: Provision, modify, and deactivate user accounts.
- **Role management**: Define roles with specific permission sets (e.g., "Agent Developer," "Knowledge Manager").
- **Permission assignment**: Assign users to roles to grant them access to specific services and capabilities.
- **Audit trails**: Review user activity logs and access patterns across the entire platform.
:::
## A unified experience
These services are not isolated applications. The suite is designed to create a seamless workflow where context flows
between them.
- **Persistent navigation**: A permanent sidebar provides one-click access to any authorized service from anywhere in
the application. You never need to return to a "home" screen to switch tasks.
- **Consistent design**: All services share the same visual design and interaction patterns. Forms, tables, and buttons
behave predictably everywhere, so learning one service helps you understand them all.
- **Cross-service context**: The interface understands the relationships between objects. When viewing an agent, you can
navigate directly to its knowledge sources or its conversation threads. When reviewing a thread, you can see which
agent participated.
- **Intelligent interface**: The interface uses modern web patterns to feel fast and responsive. Skeleton screens show
you what content is loading, and real-time updates are pushed via WebSockets for live agent execution and process
monitoring.
This integrated approach ensures that the platform is not just a collection of powerful features, but a productive and
coherent environment for all users.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents.md
title: "Agents"
description: ""
---
# Agents
Agents are specialized AI assistants that perform specific tasks through structured workflows. Unlike open-ended
chatbots, agents follow predefined steps to analyze documents, answer questions, or complete business processes.
Agents can be interactive (responding to user questions via chat) or autonomous (executing tasks automatically on a
schedule or triggered by events). The structured workflow approach makes agents predictable, transparent, and auditable
regardless of how they operate. Where a task calls for it, the Universal Agent lets the model choose among tools you
have enabled, still inside limits, permissions and approvals you set.
## What is an agent?
An agent is an AI-powered assistant configured to handle specific tasks using a predefined workflow.
Examples:
- An HR Policy Agent answers employee questions about leave policies by consulting the employee handbook (interactive,
chat-based).
- A Compliance Monitoring Agent reviews documents on a schedule, flagging potential policy violations (autonomous,
scheduled).
Agents combine large language models (LLMs) for understanding natural language with structured processes for reliable
operation.
## The standard blueprint set
The pages that follow document every agent blueprint the platform ships. A tenant does not get all of them.
Three are granted to a new tenant by default:
- **Instructed Assistant** — follows plain-text instructions you write
- **Teachable Assistant** — learns the shape of the answer you want from examples
- **Document Intelligence Assistant** — answers from your knowledge bases and cites its sources
The Universal Agent is the one profile an admin configures once for open-ended work: the model decides per message
whether to search knowledge, read the user's attached files, recall memory or use another enabled tool.
The rest are documented here because they are built, supported and ready to run — but a tenant only sees one after a
sysadmin grants it. That keeps a new tenant's catalog small enough to choose from, without taking anything away from a
tenant that already relies on an agent outside the set. Granting one takes effect immediately, with no redeploy; see
[Access control](../16_multi_tenancy/4_access_control/).
## What every chat agent can do
The conversational blueprints built on the shared capabilities (Instructed, Teachable, Document Intelligence, Company
Knowledge, MCP Tool and Universal) share the same behaviour in the conversation:
- **Attached files.** Files attached in the chat are read by the agent itself and listed as sources. A long file is cut
down to the sections that matter for the question, and the answer says so.
- **Knowledge references.** Typing `#` in Open WebUI points the agent at one of the company's knowledge collections,
limited to what the user may read.
- **Inline citations.** Statements link to the document they came from, as numbered chips in the answer.
- **Memory.** What the agent remembers about the user and the organization joins the model's instructions.
- **Chat toggles.** A chat toggle is a request to the agent, which runs it as traced, cost-attributed steps. An agent
shows a toggle only if it can serve it; today that is Code Interpreter and My Files, on the Universal Agent.
- **Refusals instead of errors.** A message larger than the model's context window, or a request outside a Teachable
Assistant's remit, is answered with a short explanation in the chat rather than an error.
How these show up in Open WebUI is described under
[Integration architecture](../10_chat_ui/11_integration_architecture/); the settings are in
[Blueprints & Profiles](2_blueprints_and_profiles/#shared-chat-settings).
## Tools the model chooses
A fixed workflow is the default, because it is predictable. Some jobs cannot be written as a fixed sequence, so an agent
can also hand the model a set of tools and let it decide, per message, which to use, run them, and decide again until it
can answer. This is the tool loop, and it is how the Universal Agent works.
The loop stays inside controls an admin sets per profile:
- **Only what is offered runs.** A tool is offered only if the profile enables it, the user's access allows it and, for
Code Interpreter or My Files, the chat toggle is on. A call to anything else is refused.
- **Approvals.** A tool can ask the user to confirm first: on every call, once per answer or once per conversation. A
declined tool is not requested again for that answer.
- **Limits.** The profile caps the number of decisions and tool calls. At the cap the model answers with what it has and
the chat says it stopped early.
- **Transparency.** Every call and result appears in the chat and in the event history, so the path to an answer can be
audited like any workflow.
## Agent "Training"
A common question is whether agents can be "trained" on company data. The Swiss AI Hub does not offer model training or
fine-tuning. Agents access current information through their knowledge bases instead.
When people ask about training an agent, they usually want the agent to know their company's specific information. The
platform accomplishes this through Retrieval-Augmented Generation (RAG). The agent retrieves relevant information from
your knowledge base when answering questions, rather than having that information embedded in the model itself.
Advantages of this approach:
- Information stays current. Update your documents and agents immediately use the new information without any
reprocessing.
- Transparency. You can see exactly which documents the agent referenced to answer each question.
- Flexibility. Different agents can access different subsets of your knowledge base by configuring which collections
they can search.
Agents "learn" by accessing an up-to-date knowledge base maintained through data pipelines. Add new documents or update
existing ones and agents automatically incorporate that information.
## How agents work
An agent's behavior follows a workflow, a predefined sequence of steps. This differs from general-purpose conversational
AI.
Example workflow for a question-answering agent:
1. Understand the request: The agent uses an LLM to interpret your question.
2. Retrieve information: The agent searches a designated knowledge base (e.g., a SharePoint folder) for relevant
documents using semantic search (RAG).
3. Synthesize the answer: The agent combines your question with retrieved information and generates a response.
4. Cite sources: The answer includes references to source documents for verification.
Workflow benefits:
- Transparency: You can see which documents the agent consulted.
- Reliability: Constraining the agent to a workflow and knowledge base reduces hallucinations and incorrect answers.
- Control: Administrators define what an agent can access and do. Agents can't access unauthorized data or perform
actions outside their workflow.
## Human-in-the-loop
Some tasks require human judgment. Agent workflows can integrate human oversight. An agent can pause and wait for your
approval before taking a step. For example, an agent might draft a customer response but wait for a support team member
to review and approve it before sending.
This lets you automate routine parts while maintaining control over decisions.
## Connecting to external tools
Agents are not limited to reading from knowledge bases — they can also take actions in other systems. Through the Model
Context Protocol (MCP), an agent connects to an external tool server and uses the tools it exposes: creating a ticket,
sending a message, or looking up a record in another application.
A connection can authenticate as the requesting user, so an external action is attributed to that person rather than to
a shared service account. The other system's audit trail and per-user permissions stay correct.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/1_fundamentals.md
title: "Agent fundamentals"
description: ""
---
# Agent fundamentals
Agents use an event-driven architecture. Users can interact through chat interfaces or agents can run autonomously.
Workflows, context management, and event handling operate in the background.
## Structured workflows
Each agent follows a predefined workflow that defines its exact sequence of operations. Instead of giving an agent tools
and letting it decide how to use them, workflows specify each step the agent takes. Where open-ended work needs it, a
workflow can include a tool loop, in which the model chooses among the tools the profile enables. The loop is one
bounded part of the workflow, with limits, approvals and an event for every decision, so it stays inspectable.
Workflows provide:
- Transparency: Developers and compliance officers can read the workflow definition and understand what the agent does
- Testability: Each step can be developed and tested independently
- Control: The agent cannot access unauthorized data or perform actions outside its workflow
Steps can use language models when needed for reasoning and natural language tasks, but many steps perform deterministic
operations like data validation, formatting, or conditional routing without any LLM involvement. The workflow controls
the overall execution path.
## Shared building blocks
Chat agents do not each reimplement the same steps. Shared building blocks, called capabilities, provide them: turning
the conversation into a query and titling the thread, recalling and storing memory, reading attached files, searching
referenced knowledge, composing everything the model sees into one set of system instructions, and completing the answer
with follow-up suggestions. A blueprint calls them in the order that fits it, so reading its workflow shows which
context reaches the model and what runs after the answer. The Instructed, Teachable, Document Intelligence, Company
Knowledge and MCP Tool agents all work this way, and the event history shows each call and the exact context the model
was given.
## Context management
Agents use three levels of context to manage state:
**Thread context** stores persistent state across multiple agent runs within the same thread. A thread represents a
continuous interaction session - for chat agents, this is a conversation; for autonomous agents, it's a series of
related executions. When you return to a conversation started yesterday, thread context lets the agent remember previous
exchanges. This context has a 30-day expiration. Access control operates at this level.
**Display context** manages what appears in the user interface. It groups actions to present them as a single
interaction. When agents collaborate, the primary agent controls whether sub-agent work appears to the user or stays
hidden.
**Run context** holds temporary data for a single agent run. It contains intermediate calculations and cached data for
that specific execution. This context expires after 30 days but is isolated between different runs.
## Event-driven architecture
Agents operate within an ecosystem of components that communicate through asynchronous messages on a message bus (NATS).
Four participants:
**Agent**: Executes business logic defined in its workflow. Receives instructions and produces results and telemetry.
**API Gateway**: Authenticates users, translates HTTP requests into internal events, and streams agent responses back to
the user interface.
**Frontend**: Receives agent output and renders it in real-time as streaming text and UI elements.
**Process Orchestrator**: Manages multi-agent workflows by monitoring when one agent completes and triggering the next
participant.
This decoupled architecture prevents issues in one component from affecting others. A slowdown in the frontend cannot
crash an agent's workflow.
## Collaboration patterns
### Human-in-the-loop
Agent workflows can pause to request human input. The workflow creates a task in the user's interface with context and
choices. The workflow stays paused (minutes, hours, or days) until the user responds, then resumes execution.
Key characteristics:
- Context preservation: The workflow resumes from the exact point it paused with full memory of intermediate results
- Audit trail: Every interaction (question, responder, decision, timestamp) gets logged
- Use cases: Regulatory approvals, quality assurance checks, ambiguous situations requiring clarification, consent
workflows
### Agent-to-agent delegation
A primary agent can delegate tasks to specialized agents. For example, a Document Inquiry Agent receiving a complex
legal question can delegate to a Legal Compliance Agent.
Benefits:
- Reusability: Build focused agents for specific tasks (entity extraction, PII detection, compliance checking) and
orchestrate them for larger problems
- Isolation: The delegated agent runs in its own workflow and cannot access the primary agent's internal state
- Visibility control: The primary agent controls the display context and can show or hide the delegation from the user
- Scalability: High-demand specialized agents can scale independently
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/2_blueprints_and_profiles.md
title: "Blueprints & Profiles"
description: ""
---
# Agent Blueprints & Profiles
The Swiss AI Hub platform separates agent definitions from their configurations. This lets administrators create
multiple customized versions of the same agent type without requiring code changes.
## Key Concepts
### Agent Blueprint
An **Agent Blueprint** is a template that defines what an agent can do:
- The workflow steps it follows (what actions it takes)
- The types of inputs it accepts (text, files, images)
- The configuration options available (model selection, parameters)
- The events it produces (what it outputs)
Blueprints are created by developers and appear automatically in the platform when agents come online. You cannot modify
a blueprint through the UI. Think of blueprints as read-only templates.
### Agent Profile
An **Agent Profile** is a configured instance of a blueprint:
- Has a unique identifier (agent ID)
- Has a custom name, description, and icon
- Uses specific settings from the blueprint's options
- Can be assigned specific permissions
You can create multiple profiles from the same blueprint. Each profile operates independently with its own
configuration. For example, from a "RAG Agent" blueprint, you could create:
- "HR Policy Agent" profile configured to search the HR document collection
- "Legal FAQ Agent" profile configured to search legal documents
- "IT Support Agent" profile configured to search IT knowledge base
Each profile is a separate agent that users can interact with.
## Managing Blueprints
### Viewing Available Blueprints
Navigate to **Admin > Agents > Blueprints** to see the agent templates available to your tenant.
The list is not the full set the platform runs. A tenant is granted a standard blueprint set when it is created, and
blueprints outside it are absent from this list rather than shown and refused. A sysadmin grants a further blueprint per
tenant in the tenant editor — no redeploy, and it appears here immediately. See
[Access control](../../16_multi_tenancy/4_access_control/) for how the ceiling is derived.
Each blueprint displays:
- **Name**: The blueprint identifier
- **Description**: What the agent does
- **Status**: Whether any instances are currently online
- **Configuration Form**: The settings available for profiles
### Blueprint Status
Blueprints show online/offline status based on whether any running agent instances have registered recently. An offline
blueprint means no agents of that type are currently running, but you can still create and configure profiles. Those
profiles will become active when the agent service starts.
## Managing Profiles
### Creating a New Profile
1. Navigate to the blueprint you want to use
2. Click **Create Profile**
3. Pick a starting point. Blueprints ship with profile templates — ready-made configurations for common use cases.
Selecting one prefills every field, leaving you to review and adjust. Skip this step to start from a blank form.
4. Fill in the required fields:
- **Agent ID**: A unique identifier (lowercase letters, numbers, underscores, hyphens)
- **Name**: Display name in the selected languages
- **Description**: What this specific profile does
- **Icon**: Visual identifier
5. Configure the agent-specific settings (model, parameters, etc.)
6. Click **Save**
Templates are curated per blueprint, not per tenant: a blueprint you can see offers every template it ships. Browse them
all under the **Templates** tab.
The profile becomes available immediately. Users with appropriate permissions can start interacting with it.
### Editing a Profile
1. Navigate to **Admin > Agents > Profiles**
2. Find and select the profile
3. Click **Edit**
4. Modify the configuration
5. Click **Save**
Changes take effect for new conversations. Existing active conversations continue with the previous configuration until
they complete.
### Deleting a Profile
1. Navigate to the profile
2. Click **Delete**
3. Confirm the deletion
Deleting a profile removes its configuration. Historical conversation data with that profile is preserved for audit
purposes. The blueprint remains available for creating new profiles.
## Configuration Options
The settings available for each profile depend on the blueprint. Common configuration options include:
### Model Selection
Choose which language model the agent uses. Options depend on which models are available through your LiteLLM
configuration.
### Task LLM
Optional second model for the agent's auxiliary work — short classification and rewriting tasks such as turning a
follow-up question into a standalone one, the relevance and context-sufficiency guards, request routing, and generating
the conversation title and follow-up suggestions. Pointing them at a smaller, cheaper model reduces cost and latency
without affecting answer quality. The exact set of steps depends on the blueprint and is listed in the tooltip next to
the **Enable Task LLM** checkbox.
Only the model is selectable: temperature and timeout are inherited from **Model Selection**, and log probabilities are
always off for these steps. Leave the whole section disabled and every step runs on the main model.
The knowledge-grounded answer the user reads is always generated by the main model. The one user-facing exception is the
reply to a meta question about the agent itself ("what can you do?"), which the task model also handles.
### Memory Model
Optional model for user-memory extraction, found inside the **User Memory** section next to the storage toggle. When a
conversation ends, the platform reads it back to distil durable facts about the user and to reconcile them against what
it already knows — dropping duplicates, replacing what has been superseded. That work is short and mechanical, so a
smaller model is usually enough, and it is worth choosing one: extraction is several model calls per stored
conversation, which makes it a meaningful share of an agent's spend.
Leave the **Enable Memory model** checkbox off and extraction runs on the platform-wide default configured at
deployment. Existing profiles are unaffected. Ticking the checkbox reveals the picker already set to that same default,
so it is a starting point to move away from rather than an empty field — saving it unchanged keeps the model the
platform would have used anyway. The picker only appears while user-memory storage is enabled, since nothing extracts
otherwise. If the agent stores memory from conversations containing images, choose a vision-capable model.
The write itself always happens in the background, after the answer has been delivered, so the chosen model never
affects how long the user waits. Two things stay deployment-wide: the embedding and reranking models, because memories
written with one embedding model cannot be searched with another; and organization memory, which stores the text an
agent hands it verbatim and runs no model at all.
### Temperature
Controls response creativity:
- Lower values (0.0-0.3): More focused, deterministic responses
- Higher values (0.7-1.0): More creative, varied responses
### Knowledge Base
For RAG-enabled agents, select which document collections the agent can search.
### Restrict to the user's access
A profile's collections are checked once, against whoever saved the profile. Without further settings, everyone allowed
to use the agent gets answers from all its collections, including ones they cannot open in the knowledge area. With
**Restrict to the user's access** on, every run is narrowed to the collections the asking user may read, using the
knowledge area's own rules. The setting is on for new profiles and off for profiles saved before it existed, so nothing
changes unannounced. If nothing is left for a user, the agent says that no knowledge is available to them instead of
answering as if the documents held no answer. Runs without a user, such as scheduled or mail-triggered ones, keep the
profile's scope. The Document Intelligence, Company Knowledge and Document Navigation assistants offer it.
### Shared chat settings
Every conversational agent (Instructed, Teachable, Document Intelligence, Company Knowledge, MCP Tool and Universal)
brings the same building blocks, each with its own section in the form:
- **User Memory** and **Organization Memory**: what the agent recalls about the user and the organization, and whether
it stores new facts. Recalled memories reach the model as part of its system instructions.
- **Attached Files**: a user can attach files in the chat and the agent reads them itself. A file that fits is read
whole; a longer one is cut down to the sections most relevant to the question, and the answer says so. The embedding
and reranking models that pick those sections are preset (`embedding/bge-m3`, `reranker/bge`) and rarely need a
change.
- **Knowledge Search**: the reranking model used when a user points the chat at a collection with `#`, or when the model
chooses to search one itself. It is preset to `reranker/bge`. Only collections the user may read are searched.
- **Condense question** and **Task LLM**: described above.
All of them cite what they read as sources in the chat. If a message alone, such as a very long pasted text, is larger
than the model's context window, the agent replies that the input is too large instead of failing; the check uses the
narrower of the answering model and the Task LLM.
### Tool loop settings
Agents that let the model choose its own tools, such as the Universal Agent, add a **Tools** section:
| Setting | What it does |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| **Maximum Decisions** | How often the model may choose tools before it must answer with what it has. |
| **Maximum Tool Calls** | How many tool calls one answer may make in total. |
| **Disable Tools** | Turn on to withhold some tools; the Disabled Tools picker appears only while it is on. |
| **Disabled Tools** | Tools of the blueprint this profile never offers, while Disable Tools is on. |
| **Approvals** | Per tool: never ask, ask for every call, ask once per answer, or ask once per conversation. |
At either limit the model answers without tools and the chat says that it stopped early. A tool is also offered only
while the chat toggle it needs (Code Interpreter or My Files) is on. See [Agents overview](../#tools-the-model-chooses).
### System Prompt
Some agents allow customizing the system prompt to adjust behavior and personality.
## Permissions
Access to profiles follows the platform's permission system:
- **Blueprint access** (`aihub.admin.agent.{blueprint}`): Create profiles from a specific blueprint. This is what a
tenant is granted for each of its standard blueprints; it does not carry the profiles already built from them.
- **Profile access** (`aihub.user.agent.{blueprint}.{profile_id}`): Use a specific profile. A profile's own rule is
added to the creating tenant's ceiling automatically, so a tenant reaches the profiles it builds.
- **Wildcard access** (`aihub.user.agent.*.>`): Access all agents (typically for administrators). Deployment-wide, not
per tenant — profiles are not separated by tenant in storage, so grant it deliberately.
See the [Access Management](../../11_access_management/) section for details on configuring permissions.
## Best Practices
### Naming Conventions
- Use descriptive profile names that indicate the purpose ("HR Policy Assistant" not "RAG Agent 1")
- Keep agent IDs short but meaningful (`hr_policy`, `legal_faq`)
- Include the target audience in descriptions ("For employees with HR questions")
### Configuration Strategy
- Start with conservative settings (lower temperature, smaller context windows)
- Test with representative questions before deploying widely
- Create separate profiles for different use cases rather than one generic profile
### Lifecycle Management
- Review profile usage periodically through audit logs
- Retire unused profiles to reduce confusion
- Document configuration choices for team knowledge sharing
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/3_instructed_assistant.md
title: "Instructed Assistant"
description: "A configurable chat assistant that follows your plain-text instructions, with no document retrieval."
---
# Instructed Assistant
The **Instructed Assistant** is the simplest agent blueprint on the platform. It wraps a language model directly: you
give it a set of plain-text instructions (a system prompt), and it answers the user's messages accordingly. There is no
document retrieval, no knowledge base, and no human escalation — just a configured model that behaves the way your
instructions describe.
Think of it as a focused, branded version of a general chatbot. Where a raw model has no personality and no rules, an
Instructed Assistant profile can be told *who it is*, *what tone to use*, *what it should and shouldn't do*, and *which
model to run on* — all without writing any code.
::: tip When to reach for this agent
Use the Instructed Assistant when the task only needs the model's own reasoning and language skills — drafting,
rewriting, summarising pasted text, brainstorming, translation, or answering general questions. If the assistant needs
to look things up in your company's documents, use the
[Document Intelligence Assistant](../5_document_intelligence_assistant/) instead.
:::
## What it does
The Instructed Assistant runs a short, fixed workflow every time a user sends a message:
```mermaid
flowchart LR
A[User message] --> B[Trim history,
derive the query]
B --> C[Gather memory,
files, referenced knowledge]
C --> D[Compose the
model's context]
D --> E[Stream the answer]
```
1. **Prepare the conversation.** The chat history is trimmed so it fits inside the model's input budget and the thread
gets its title. If the message alone is larger than the model's context window, the assistant says so in the chat and
stops, rather than failing with an error.
2. **Gather context.** In parallel, it recalls what it remembers about the user and the organization, reads any files
the user attached, and searches any knowledge collections the user pointed it at with `#`. Each is empty when it does
not apply.
3. **Compose.** Your system prompt and whatever was gathered are merged into the model's system instructions, within the
input budget. The event history shows exactly what the model was given.
4. **Answer.** The response is streamed back token by token, with follow-up suggestions, and new facts about the user
are stored in the background.
There is no search of your document collections unless the user asks for one with `#`, so the assistant stays fast,
cheap, and predictable, which makes it a good default for assistants that don't need to consult your data.
## What it does *not* do
It helps to be explicit about the boundaries, because the name "assistant" can suggest more than this blueprint offers:
- **No knowledge base of its own.** It searches your documents only for collections a user references with `#`, and
reads files a user attaches. If you ask it about an internal policy without either, it will answer from the model's
general training. For grounded, cited answers on a fixed set of collections, use the
[Document Intelligence Assistant](../5_document_intelligence_assistant/).
- **No tools or actions.** It cannot create tickets, send messages, or call external systems. For that, use the MCP Tool
Agent, or the Universal Agent for tools the model chooses.
- **No human escalation.** It will not hand a question off to a colleague. For that, see the
[Expert Coordinator Agent](../9_expert_coordinator_agent/).
- **No "training" on your data.** Like every agent on the platform, it is not fine-tuned on your content. Its behaviour
comes entirely from the system prompt and the chosen model. See the [Agents overview](../) for why the platform uses
configuration and retrieval rather than training.
## Typical scenarios
- **A writing helper.** "You are a helpful assistant that rewrites text to be clear and concise. Always keep the
original meaning." Employees paste a paragraph and get a cleaned-up version.
- **A tone-of-voice assistant.** A marketing team configures the system prompt with their brand voice and style rules,
giving everyone a consistent drafting helper.
- **A translation assistant.** "Translate everything the user sends into formal German. Output only the translation."
- **A constrained Q&A bot.** A general-knowledge helper that is instructed to stay on a specific topic and politely
decline anything else.
## Setting it up
Like every agent, the Instructed Assistant is delivered as a **blueprint** (the read-only template) from which you
create one or more **profiles** (your configured, named instances). If you are new to that distinction, read
[Blueprints & Profiles](../2_blueprints_and_profiles/) first. To create a working assistant:
1. **Open the blueprint.** Go to **Admin > Agents > Blueprints** and select **Instructed Assistant**. If it shows as
offline, you can still configure profiles — they activate once the agent service is running.
2. **Create a profile.** Click **Create Profile** and give it an **Agent ID** (a short, URL-safe slug such as
`writing-helper`), a **Name**, a **Description**, and an **Icon**. These are what users see when they pick an
assistant to talk to.
3. **Write the system prompt.** This is the heart of the assistant. Describe its role, its tone, what it should do, and
what it must refuse. Be concrete — see the best practices below.
4. **Choose the model.** Pick a chat model from the dropdown. The available options come from your platform's LiteLLM
configuration. Match the model to the task: a smaller, cheaper model is fine for rewriting and translation; a larger
one helps for nuanced reasoning.
5. **Tune the parameters (optional).** Adjust temperature, the input-token budget, and the timeout if the defaults don't
suit your use case. The defaults are sensible for most assistants.
6. **Save.** The profile is available immediately to any user with permission to use it.
::: tip There is nothing else to set up
Unlike the document- or expert-oriented agents, the Instructed Assistant has no external dependencies. You do **not**
need to create a knowledge base, run a data pipeline, or connect a chat channel. A model and a system prompt are all it
needs.
:::
## Configuration reference
The configuration form is grouped into three parts: the profile identity (shared by every agent), the assistant's
behaviour, and the language-model settings.
### Profile identity
These fields exist on every agent blueprint and define how the profile appears in the UI.
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | --------------------------------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier for this profile. Lowercase letters, digits, underscores, hyphens only. |
| **Name** | Text (per language) | Yes | Display name shown to users. Can be set per language (de, en, fr, it). |
| **Description** | Text (per language) | Yes | Short explanation of what this profile is for. Shown in the assistant picker. |
| **Icon** | Icon picker | No | Visual identifier. Defaults to a generic robot icon. |
### Behaviour
These fields control how the assistant treats each conversation.
| Field | Type | Default | Description |
| ------------------------ | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **System Prompt** | Long text | *(empty)* | The plain-text instructions that define the assistant's role, tone, and rules. Inserted at the start of every conversation. This is the single most important setting. |
| **Maximum Input Tokens** | Number | `100000` | The size of the input budget. The conversation (system prompt + chat history + new message) is trimmed to fit. Lower values cut cost and keep the model focused on recent turns; higher values preserve more history. Range: 1,000–200,000. |
::: warning Match the input budget to your model
**Maximum Input Tokens** must stay within the chosen model's actual context window. Setting it higher than the model
supports won't expand the model — the model will simply reject or truncate the request. The assistant also checks the
message against the model's real window and answers with a short notice when it cannot fit. When in doubt, leave it at
the default.
:::
The form also contains the shared chat sections (memory, attached files, referenced knowledge, task model). See
[Shared chat settings](../2_blueprints_and_profiles/#shared-chat-settings).
### Language model
These settings live under the model section of the form. They select the model and control how it generates text.
| Field | Type | Default | Description |
| ---------------------------- | ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Model** | Model picker | — | Which chat model the assistant runs on. Options come from your LiteLLM configuration. Required. |
| **Temperature** | Number | `0.0` | Controls randomness. `0.0` gives focused, repeatable answers (best for translation, extraction, factual replies). Higher values (up to `2.0`) give more varied, creative output. |
| **Return Log Probabilities** | Toggle | Off | Whether the model returns token-level probabilities. An advanced/diagnostic option; leave off unless you specifically need confidence scores. |
| **Top Log Probabilities** | Number | `0` | How many of the most likely alternative tokens to return per position. Only applies when log probabilities are enabled. Range: 0–20. |
| **Timeout** | Number (seconds) | `600` | How long to wait for the model before giving up. Increase only if you use a slow model with very long outputs. |
::: details How the settings combine at runtime
When a message arrives, the assistant builds the conversation as
`[ system messages → your system prompt → chat history → new user message ]`, trims it to **Maximum Input Tokens**, then
calls the selected **Model** with the configured **Temperature**, **Timeout**, and log-probability options. The reply
streams straight back to the chat UI.
:::
## Best practices
**Write the system prompt like a job description.** State the role ("You are an IT support assistant for internal
staff"), the behaviour ("Answer in short, numbered steps"), and the limits ("If a question is not about IT, say you can
only help with IT topics"). Vague prompts produce vague assistants.
**Start with a low temperature.** `0.0`–`0.3` is right for most business assistants — it makes answers consistent and
predictable. Only raise it for genuinely creative tasks like brainstorming or copywriting.
**Keep one profile per purpose.** Rather than one "does everything" assistant, create focused profiles ("Email Drafter",
"German Translator", "IT FAQ Bot"). Each gets a tighter system prompt and is easier for users to choose between.
**Pick the smallest model that does the job.** Rewriting, translating, and summarising rarely need your largest model.
Reserve the expensive models for profiles that genuinely need stronger reasoning.
**Remember it can't see your documents.** If users keep asking it about internal policies or files, that's a signal you
need the [Document Intelligence Assistant](../5_document_intelligence_assistant/) instead.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/4_teachable_assistant.md
title: "Teachable Assistant"
description: "A chat assistant you teach by example — give it sample question-and-answer pairs and it mimics your style."
---
# Teachable Assistant
The **Teachable Assistant** (the few-shot agent) is shaped by examples rather than by a long set of written rules. You
give it a handful of sample exchanges — *when a user asks something like this, answer like that* — and it learns the
pattern, tone, and format you want from those examples. This is the technique known as **few-shot prompting**: the model
is shown a few "shots" of the desired behaviour and generalises from them.
Like the [Instructed Assistant](../3_instructed_assistant/), it has no knowledge base and takes no external actions. The
difference is *how you steer it*: the Instructed Assistant follows written instructions, while the Teachable Assistant
also follows demonstrated examples — which is often far easier than describing a desired style in words.
::: tip When to reach for this agent
Use the Teachable Assistant when the *style or shape* of the answer matters and is easier to show than to explain —
classifying messages into fixed categories, replying in a strict format, matching a support tone of voice, or extracting
fields from text. If you only need free-form help with no particular format, the
[Instructed Assistant](../3_instructed_assistant/) is simpler. If answers must come from your documents, use the
[Document Intelligence Assistant](../5_document_intelligence_assistant/).
:::
## What it does
For every message, the Teachable Assistant first checks whether the request is something it should handle at all, and
only then answers — using your examples as the guide:
```mermaid
flowchart LR
A[User message] --> B{Fits this
assistant?}
B -- No --> E[Politely decline]
B -- Yes --> C[Condense to a
standalone question]
C --> D[Prime with your
examples]
D --> F[Answer in your style]
```
1. **Check fit (suitability guard).** The assistant compares the request against its own description. If the question is
clearly outside what this assistant is for, it declines instead of answering badly. This is what keeps a narrowly
trained assistant from being dragged off-topic. The decline is written in the chat, with the guard's reason, so the
user is never left with an empty answer.
2. **Condense the question.** The recent conversation and the latest message are folded into one self-contained
question, so follow-ups like "and the second one?" still make sense on their own.
3. **Prime with your examples.** Your system prompt and your example pairs are assembled into the context the model
sees, followed by the condensed question.
4. **Answer in your style.** The model responds, following the pattern your examples established, and the answer is
streamed back to the user.
Before any of this, the assistant checks that the message fits the model's context window. A message that cannot fit,
for example a very long pasted text, gets a short reply asking the user to shorten it or attach the text as a file
instead, since attached files are cut down to the sections that matter.
::: warning The examples replace the raw chat history
At the answering step the assistant deliberately uses your **examples** as the conversational context rather than the
real chat history — the history only survives through the condensed question. This is what makes the examples so
influential: they are effectively the conversation the model thinks it is continuing. Choose them carefully.
:::
## What it does *not* do
- **No knowledge base of its own.** It has no configured collections to search; answers come from the model plus your
examples, and only the files attached to a message and the collections referenced with `#` are read. For grounded,
cited answers over a fixed knowledge base use the
[Document Intelligence Assistant](../5_document_intelligence_assistant/).
- **No tools or actions, no human escalation.** Same boundaries as the Instructed Assistant.
- **No real "training".** Despite the name, nothing is fine-tuned. "Teaching" here means supplying examples in the
configuration — they take effect immediately and can be changed at any time. See the [Agents overview](../) for why
the platform uses examples and retrieval rather than model training.
## Typical scenarios
- **Message classification.** Examples pair incoming messages with a single category label ("Billing", "Technical",
"Other"); the assistant labels new messages the same way.
- **Strict output format.** Examples show input text and a desired JSON or template response; the assistant produces the
same shape for new inputs.
- **Tone matching.** A support team supplies real (sanitised) question/answer pairs in their house style, and the
assistant drafts new replies that sound the same.
- **Field extraction.** Examples demonstrate pulling specific fields (dates, names, amounts) out of free text.
## Setting it up
The Teachable Assistant is delivered as a **blueprint** from which you create configured **profiles** — see
[Blueprints & Profiles](../2_blueprints_and_profiles/) for that distinction. To create one:
1. **Open the blueprint.** Go to **Admin > Agents > Blueprints** and select **Teachable Assistant**.
2. **Create a profile** with an **Agent ID**, **Name**, **Icon**, and — importantly — a precise **Description** (see the
note below).
3. **Write the system prompt.** A short instruction describing the task ("You classify support messages into one of
three categories").
4. **Add your examples.** This is the core of the setup. Add several **example pairs**, each with the user's input and
the exact response you want. Three to ten well-chosen examples usually beat a long written prompt.
5. **Choose the model and parameters.** Pick a chat model and set the temperature — low for classification and
formatting, where consistency matters.
6. **Save**, then test with inputs that resemble (but don't copy) your examples, plus a few off-topic ones to confirm
the suitability guard declines them.
::: warning The Description field is functional, not just a label
Unlike most agents, the Teachable Assistant uses its **Description** as the test for the suitability guard. A vague
description ("A helpful assistant") makes the guard let everything through; a precise one ("Classifies internal IT
support messages into Billing, Technical, or Other") lets it correctly turn away unrelated questions. Write the
description as a tight statement of scope.
:::
## Configuration reference
The form has four parts: the profile identity, the system prompt and examples, the input budget, and the language model.
### Profile identity
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier. Lowercase letters, digits, underscores, hyphens. |
| **Name** | Text (per language) | Yes | Display name shown to users. |
| **Description** | Text (per language) | Yes | **Doubles as the suitability test.** Describe precisely what this assistant is for — the guard uses it to decide whether to answer. |
| **Icon** | Icon picker | No | Visual identifier. Defaults to a book icon. |
### Teaching the assistant
These fields define what the assistant learns from.
| Field | Type | Default | Description |
| --------------------- | ------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **System Prompt** | Long text | *(empty)* | A short instruction describing the task, used alongside the examples. |
| **Few-Shot Examples** | List of pairs | *(empty)* | The example exchanges that teach the assistant. Each entry has a **user** input and the desired **agent** response. Both can be per-language. |
Each example is one pair:
| Field | Type | Description |
| --------- | ------------------- | ----------------------------------------------------------- |
| **User** | Text (per language) | An example of what a user might say. |
| **Agent** | Text (per language) | The response you want the assistant to give for that input. |
### Input budget
| Field | Type | Default | Description |
| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Max Input Tokens** | Number | `100000` | Size of the input budget; the conversation is trimmed to fit. Range 1,000–200,000. Keep it within the chosen model's context window. |
### Language model
| Field | Type | Default | Description |
| ---------------------------- | ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Model** | Model picker | — | Which chat model the assistant runs on. Options come from your LiteLLM configuration. Required. |
| **Temperature** | Number | `0.0` | Randomness. Keep low (`0.0`–`0.3`) for classification and strict formatting; raise only for creative tasks. Range 0.0–2.0. |
| **Return Log Probabilities** | Toggle | Off | Advanced/diagnostic option for token-level confidence scores. Leave off unless needed. |
| **Top Log Probabilities** | Number | `0` | How many alternative tokens to report per position; only applies when log probabilities are enabled. Range 0–20. |
| **Timeout** | Number (seconds) | `600` | How long to wait for the model before giving up. |
::: details How the settings combine at runtime
A message is trimmed to **Max Input Tokens**, checked against the **Description** by the suitability guard, and (if
accepted) condensed into a standalone question. The assistant then builds the context as
`[ system prompt → your example pairs → condensed question ]` and calls the selected **Model** with the configured
**Temperature**, **Timeout**, and log-probability options, streaming the reply to the chat UI.
:::
## Best practices
**Show, don't tell.** A few concrete example pairs steer behaviour more reliably than a long written prompt. Lead with
examples; keep the system prompt short.
**Cover the variety, not the volume.** Choose examples that span the different *kinds* of input you expect (the easy
case, the tricky case, the edge case) rather than many near-duplicates. Three to ten diverse examples is a good range.
**Make every example identical in shape to the answer you want.** The assistant copies format as much as content — if
you want JSON out, every example's response must be valid JSON; if you want a one-word label, every example must be a
single word.
**Write a tight Description.** It is the on/off switch for the suitability guard. State exactly what's in scope so
unrelated questions are declined cleanly.
**Use a low temperature for structured tasks.** Classification, extraction, and formatting benefit from `0.0`–`0.2` so
the output stays consistent across runs.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/5_document_intelligence_assistant.md
title: "Document Intelligence Assistant"
description: "Answers questions by retrieving and citing information from your internal documents (RAG)."
---
# Document Intelligence Assistant
The **Document Intelligence Assistant** (the RAG agent) answers questions using your organization's own documents. It
uses Retrieval-Augmented Generation (RAG): before answering, it searches your knowledge bases for the most relevant
passages and grounds its reply in those passages — with citations — rather than relying on the language model's general
training. It is the most capable and most configurable agent on the platform, and the one most enterprise deployments
build around.
Unlike the [Instructed Assistant](../3_instructed_assistant/) and [Teachable Assistant](../4_teachable_assistant/),
which only use the model's own knowledge, this agent can answer questions about *your* content — policies, manuals,
reports, project files — and tell the user exactly which documents it drew from.
::: tip About "training" agents
The Swiss AI Hub does not fine-tune or train models on your data. The Document Intelligence Assistant stays current by
*retrieving* from your knowledge base at query time. Update a document and the agent uses the new version on the next
question — no retraining, and you can always see which sources it used. See the [Agents overview](../) for more.
:::
## What it does
At its core the agent runs a retrieve-then-answer loop: it turns the conversation into a clean search query, fetches and
ranks the most relevant passages, checks whether what it found is actually enough to answer, and only then writes a
grounded, cited reply.
```mermaid
flowchart LR
A[Question] --> B[Condense to a
standalone question]
B --> C[Search the
knowledge base]
C --> D[Rerank by
relevance]
D --> E{Enough
context?}
E -- No --> C
E -- Yes --> F[Answer with
citations]
```
1. **Condense the question.** The recent conversation and the latest message are folded into one self-contained
question, so follow-ups like "and what about part-timers?" still search correctly on their own.
2. **Search the knowledge base.** Semantic search runs across the configured knowledge bases (vector-indexed collections
of your documents) and returns the most relevant passages.
3. **Rerank by relevance** *(optional).* A dedicated reranking model re-scores the passages so the best ones rise to the
top before they reach the language model.
4. **Check sufficiency** *(optional).* A guard asks whether the retrieved passages are actually enough to answer. If
not, the agent can search again with a refined query ("multi-hop"), up to a configured limit — or, if it still can't
find enough, tell the user it doesn't know rather than guess.
5. **Answer with citations.** The model writes the answer using only the retrieved passages and references its sources.
Users can also bring their own material into the conversation. Files attached in the chat are read by the agent and used
next to the configured knowledge (a long file is cut to the sections that matter for the question, and the answer says
so). Typing `#` in Open WebUI adds one of the company's collections to the search for that message. Every document the
model read appears as a numbered citation chip in the answer, labelled with the document's title.
Beyond this core loop, the agent can also draw on **memory** — personal context it has learned about the individual
user, and shared organizational knowledge — and apply a **suitability guard** that politely declines questions outside
its remit. These are all optional and covered in the configuration reference below.
## What it does *not* do
- **It won't answer from general knowledge.** By design it answers from your documents. If nothing relevant is found
(and the sufficiency guard is on), it says so rather than inventing an answer.
- **No tools or actions.** It reads and answers; it doesn't create tickets or call external systems. For that, use the
MCP Tool Agent, or the Universal Agent when the model should choose between searching knowledge and other tools.
- **No human escalation on its own.** If you want it to fall back to a human expert when it can't answer, use the
Company Knowledge Agent (the expert-RAG variant). See the [Expert Coordinator Agent](../9_expert_coordinator_agent/).
- **It does not ingest documents.** Filling and updating the knowledge base is the job of a
[data pipeline](../../6_pipelines/), not the agent. The agent only reads what the pipeline has indexed.
## Typical scenarios
- **HR policy assistant.** Configured to search the HR knowledge base, it answers "How many vacation days do I carry
over?" with a citation to the staff handbook.
- **Technical support assistant.** Points at product manuals and release notes; answers customer-facing questions with
references to the exact section.
- **Project knowledge assistant.** Searches a project's documents so team members can ask about decisions, specs, and
status without digging through folders.
## Before you start: prerequisites
This is the key difference from the simpler agents — the Document Intelligence Assistant depends on infrastructure that
must exist **independently of the agent itself**. Set these up first:
1. **A populated knowledge base.** The agent searches vector-indexed collections of your documents. Those collections
must already exist and contain indexed content. Creating and filling them is done by a
[data ingestion pipeline](../../6_pipelines/) — the default pipeline indexes documents uploaded through the UI, and
custom pipelines can sync from sources like SharePoint and keep the index up to date as documents change. **No
knowledge base, nothing to retrieve.**
2. **An embedding model.** Search works by comparing the embedding (vector) of the question against the embeddings of
your documents. The embedding model you select for the agent **must match the one used to index the knowledge base**
— otherwise the vectors are incompatible and search returns nothing useful.
3. **A chat model.** The language model that writes the final answer, available through your platform's LiteLLM
configuration.
4. **A reranking model** *(optional).* Required only if you enable reranking. Also made available through LiteLLM.
5. **A memory backend** *(optional).* Required only if you enable user or organization memory.
::: warning The embedding model must match the index
The single most common cause of "the agent finds nothing" is an embedding-model mismatch between the agent configuration
and the pipeline that built the knowledge base. Confirm both use the same embedding model before debugging anything
else.
:::
## Setting it up
The agent is delivered as a **blueprint** from which you create configured **profiles** — see
[Blueprints & Profiles](../2_blueprints_and_profiles/). With the prerequisites in place:
1. **Open the blueprint** under **Admin > Agents > Blueprints** and select **Document Intelligence Assistant**.
2. **Create a profile** with an **Agent ID**, **Name**, **Description**, and **Icon**.
3. **Add at least one knowledge source.** Pick the vector store (knowledge base) to search and the matching embedding
model. This is mandatory — the agent needs somewhere to retrieve from. You can add several sources to search them
together.
4. **Choose the chat model** and adjust its temperature (keep it low for factual, grounded answers).
5. **Tune retrieval and answering** as needed: how many passages to fetch, whether to rerank, whether to run the
sufficiency guard and allow multi-hop, and the system prompt.
6. **Enable memory and guards** if you want them (all optional — see below).
7. **Save and test** with real questions, checking that the cited sources are the ones you expect.
## Configuration reference
The form has a lot of options because the agent is powerful. Only the **profile identity**, **chat model**, and **at
least one knowledge source** are required; everything else has sensible defaults.
### Profile identity
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier. Lowercase letters, digits, underscores, hyphens. |
| **Name** | Text (per language) | Yes | Display name shown to users. |
| **Description** | Text (per language) | Yes | Short explanation shown in the assistant picker. |
| **Icon** | Icon picker | No | Visual identifier. Defaults to a file icon. |
### Language model
| Field | Type | Default | Description |
| ---------------------------- | ---------------- | ------- | ------------------------------------------------------------------------------------------ |
| **Model** | Model picker | — | The chat model that writes the answer. Required. |
| **Temperature** | Number | `0.0` | Randomness. Keep low (`0.0`–`0.3`) for grounded, factual answers. Range 0.0–2.0. |
| **Return Log Probabilities** | Toggle | Off | Advanced diagnostic option for token-level confidence. Leave off unless needed. |
| **Top Log Probabilities** | Number | `0` | Alternative tokens to report per position; only when log probabilities are on. Range 0–20. |
| **Timeout** | Number (seconds) | `600` | How long to wait for the model before giving up. |
### Knowledge sources (retrievers)
The agent searches one or more **knowledge sources**. Add at least one; add several to search multiple knowledge bases
together. Each source has these settings:
| Field | Type | Default | Description |
| -------------------------- | --------------------- | --------- | --------------------------------------------------------------------------------------------------- |
| **Embedding model** | Model picker | — | Must match the model used to index this knowledge base (see prerequisites). Required. |
| **Vector store** | Knowledge-base picker | — | The collection (and optional namespaces) to search. Required. |
| **Retrieve K** | Number | `5` | How many passages to fetch per search. Higher finds more but adds noise and cost. Range 1–100. |
| **Query mode** | Choice | `default` | Search strategy: `default` (dense/semantic), `hybrid` (dense + keyword), or `sparse` (keyword). |
| **Node types** | Multi-select | `content` | Whether to retrieve document **content**, parent **summary** nodes, or both. At least one required. |
| **Retrieve previous/next** | Optional group | Off | Also pull the chunks immediately before/after each hit, so passages keep their surrounding context. |
| **Retrieve summaries** | Optional group | Off | Also pull parent-level summary nodes for a higher-level view of the source document. |
### Restrict to the user's access
| Field | Type | Default | Description |
| --------------------------------- | ------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Restrict to the user's access** | Toggle | On for new profiles, off for older ones | Retrieve only from the configured collections the person asking may read in the knowledge area. Without it, everyone who can use this agent gets answers from all its collections. |
If a user may read none of the configured collections, the agent says that no knowledge is available to them. Runs
without a user (scheduled or triggered by mail) keep the profile's scope. See
[Restrict to the user's access](../2_blueprints_and_profiles/#restrict-to-the-user-s-access).
### Reranking *(optional)*
Off by default. When enabled, a dedicated model re-scores retrieved passages for relevance before they reach the chat
model — usually a meaningful quality boost at some extra latency and cost.
| Field | Type | Default | Description |
| ------------------- | ------------ | ------- | --------------------------------------------------------------------------------- |
| **Reranking model** | Model picker | — | The rerank model (available through LiteLLM). Required when reranking is enabled. |
| **Top N** | Number | `5` | How many passages to keep after reranking. Range 1–100. |
### Context-sufficiency guard *(optional)*
Off by default. When enabled, a guard checks whether the retrieved context is actually enough to answer — and can
trigger additional retrieval rounds ("multi-hop") or have the agent admit it doesn't know instead of guessing.
| Field | Type | Default | Description |
| -------------------------------- | --------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Check context sufficiency** | Toggle | Off | Run the guard before answering. Strongly recommended for high-stakes use where a wrong answer is worse than "I don't know." |
| **Max retrieval hops** | Number | `1` | How many times the agent may re-search when the guard finds the context insufficient. Higher can improve answers but adds latency. Range 1–10. |
| **Context-insufficient message** | Long text | *(default provided)* | What the agent says when it can't find enough to answer. Customize the wording and tone. |
### Suitability guard *(optional)*
A few-shot guard that decides whether a question is in scope for this assistant. Provide example requests labelled
accept/reject; leave the list empty to accept everything.
| Field | Type | Description |
| ------------------ | ------------------- | --------------------------------------------------------------- |
| **User request** | Text (per language) | An example request a user might send. |
| **Should accept?** | Toggle | Whether this example should be accepted (in scope) or rejected. |
| **Reason** | Text (per language) | Why it's accepted or rejected — helps the guard generalize. |
### User memory *(optional)*
Lets the assistant remember context about the individual user across conversations (e.g. their role or preferences) and
use it to personalize answers. On by default.
| Field | Type | Default | Description |
| -------------------------------- | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Enable user memory retrieval** | Toggle | On | Pull personal memories into context to personalize answers. |
| **Rerank user memory** | Toggle | On | Rerank retrieved memories for relevance (shown only when retrieval is enabled). Adds cost. |
| **Enable user memory storage** | Toggle | On | Save new learnings from the conversation for future personalization. |
| **Memory model** | Model picker | Platform default | Model that distils and reconciles those learnings. Off = the platform-wide model; enabling it starts from that same one. Needs storage on. |
Saving happens in the background once the answer has been delivered, so none of it adds to the wait.
### Organization memory *(optional)*
Lets the assistant draw on shared knowledge captured for the whole organization (for example, answers gathered by the
[Expert Coordinator Agent](../9_expert_coordinator_agent/)). Set the organization-memory section to off to disable it.
| Field | Type | Default | Description |
| ------------------------------ | ------ | ---------------- | ----------------------------------------------------------------------------------------------- |
| **Tenant ID** | Text | platform default | Which tenant's shared memory to read. |
| **Allowed namespaces** | List | *(empty)* | Allow-list of memory namespaces to read from. Empty means unrestricted. |
| **Default namespace** | Text | platform default | Namespace used when a request doesn't specify one. Must be within the allow-list if one is set. |
| **Rerank organization memory** | Toggle | On | Rerank retrieved organization memories for relevance. Adds cost. |
### Attached files and referenced knowledge
Two sections with preset models cover material users bring into the chat. Most profiles never change them.
| Field | Default | Description |
| -------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- |
| **Attached Files > Embedding Model** | `embedding/bge-m3` | Shortlists the sections of an attached file that is too long to fit whole. |
| **Attached Files > Reranking Model** | `reranker/bge` | Orders the shortlisted sections by relevance before they fill the room left in the prompt. |
| **Knowledge Search > Reranking Model** | `reranker/bge` | Orders what the collections a user referenced with `#` return. Each is searched with its own embedding model. |
The agent reserves room for its retrieved passages before it sizes attached files, so a large attachment cannot crowd
out the knowledge search.
### Prompts and input budget
| Field | Type | Default | Description |
| -------------------- | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **System prompt** | Long text | *(grounding default)* | Defines the assistant's role and rules. The default instructs it to answer only from retrieved context and quote sources. |
| **Context prompt** | Long text | *(template default)* | Template for how retrieved passages are presented to the model. Most deployments leave this at the default. |
| **Max input tokens** | Number | `128000` | The input budget; the conversation and retrieved context are trimmed to fit. Keep within the chat model's context window. Range 1,024–128,000. |
If a message alone is larger than the chat model's context window, the agent replies that the input is too large (and
suggests a smaller file) instead of ending in an error. The check uses the real window of the model, not only this
budget.
## Best practices
**Get the knowledge base right first.** The quality of answers is capped by the quality of what's indexed. Make sure the
[pipeline](../../6_pipelines/) is ingesting the right documents and keeping them current before tuning the agent.
**Match the embedding model to the index.** Re-read the prerequisites — a mismatch silently breaks retrieval.
**Turn on the sufficiency guard for high-stakes assistants.** For policy, legal, or compliance use, it's far better for
the agent to say "I don't have enough information" than to produce a confident wrong answer. Pair it with a small number
of multi-hop retries.
**Keep temperature low.** `0.0`–`0.3` keeps answers faithful to the retrieved sources.
**Start with a few knowledge sources, not all of them.** Searching every collection at once produces noisy, mixed
results. If users span very different topics, consider the Document Navigation Assistant to route each question to the
right knowledge base instead of dumping them all into one profile.
**Tune Retrieve K and reranking together.** A common pattern is to retrieve a generous number of passages (higher
Retrieve K) and let reranking keep only the best few (Top N) — more recall without overwhelming the model.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/6_retrieval_agent.md
title: "Retrieval Agent"
description: "A headless building block that retrieves and returns relevant document passages — without generating an answer."
---
# Retrieval Agent
The **Retrieval Agent** is the [Document Intelligence Assistant](../5_document_intelligence_assistant/) with the answer
step removed. It performs the *retrieval* half of RAG — searching your knowledge base and returning the most relevant
document passages — but it **never calls a language model and never writes an answer**. It hands back the raw context
(the matched passages and their source metadata) for something else to use.
Think of it as a focused building block rather than a finished chat assistant. Where the Document Intelligence Assistant
answers a user's question, the Retrieval Agent just *finds the relevant material* and stops.
::: warning A specialist component, not a general chat assistant
This is an advanced, composable agent. It does not take a normal chat message and does not produce a conversational
reply — it takes a question and returns context. In the current platform it is **not wired into any other agent's
workflow** (for example, the Document Navigation Assistant delegates to a full RAG agent, not to this one). Reach for it
only when you specifically want retrieval decoupled from answer generation. If you want an assistant that answers
questions in chat, use the [Document Intelligence Assistant](../5_document_intelligence_assistant/).
:::
## What it does
```mermaid
flowchart LR
A[Question] --> B[Search the
knowledge base]
B --> C[Order by
source document]
C --> D[Return context
+ sources]
```
1. **Search the knowledge base.** Semantic search runs against the configured knowledge base and returns the most
relevant passages. It can optionally pull adjacent chunks so each passage keeps its surrounding context.
2. **Order by source document.** The retrieved passages are grouped and ordered by the document they came from, and
formatted into a single context block.
3. **Return context and sources.** The agent finishes by returning the ordered context together with the structured
source passages — and stops. There is no answer-generation step.
## When to use it
- **Retrieval decoupled from generation.** When you want to fetch grounding material and feed it to a separate process —
a custom front-end, another system, or a model you control yourself.
- **Inspecting and debugging retrieval.** When you want to see exactly what a knowledge base returns for a given
question, without an LLM's answer in the way.
- **Composition.** As a reusable retrieval primitive in custom flows where answer generation happens elsewhere.
If you simply want grounded, cited answers in chat, this is the wrong agent — use the
[Document Intelligence Assistant](../5_document_intelligence_assistant/), which wraps this same retrieval step and then
answers.
## What it deliberately leaves out
Compared to the Document Intelligence Assistant, the Retrieval Agent omits everything that isn't retrieval:
| Capability | Document Intelligence Assistant | Retrieval Agent |
| ------------------------------------- | :-----------------------------: | :--------------------------: |
| Searches your knowledge base | ✅ | ✅ |
| Reranking | ✅ (optional) | ❌ |
| Generates an answer with an LLM | ✅ | ❌ |
| Citations in a written reply | ✅ | ❌ (returns sources as data) |
| Context-sufficiency guard / multi-hop | ✅ (optional) | ❌ |
| Suitability guard | ✅ (optional) | ❌ |
| User & organization memory | ✅ (optional) | ❌ |
This is what makes its configuration so small: there is no chat model, no temperature, no reranking, no guards, and no
memory to configure — only the knowledge source.
## Before you start: prerequisites
The Retrieval Agent shares the Document Intelligence Assistant's data prerequisites — but **no chat model is needed**,
since it never generates text.
1. **A populated knowledge base.** A vector-indexed collection of your documents must already exist, filled by a
[data ingestion pipeline](../../6_pipelines/). The agent only reads what the pipeline has indexed.
2. **An embedding model** that **matches the one used to index the knowledge base** — a mismatch silently breaks search.
## Configuration reference
Only the **profile identity** and a single **knowledge source** are required.
### Profile identity
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier. Lowercase letters, digits, underscores, hyphens. |
| **Name** | Text (per language) | Yes | Display name. |
| **Description** | Text (per language) | Yes | Short explanation of what this profile retrieves from. |
| **Icon** | Icon picker | No | Visual identifier. |
### Knowledge source
The single knowledge base this agent searches. (The Document Intelligence Assistant allows several; the Retrieval Agent
takes exactly one.)
| Field | Type | Default | Description |
| -------------------------- | --------------------- | --------- | -------------------------------------------------------------------------------------------- |
| **Embedding model** | Model picker | — | Must match the model used to index this knowledge base. Required. |
| **Vector store** | Knowledge-base picker | — | The collection (and optional namespaces) to search. Required. |
| **Retrieve K** | Number | `5` | How many passages to fetch. Range 1–100. |
| **Query mode** | Choice | `default` | Search strategy: `default` (semantic), `hybrid` (semantic + keyword), or `sparse` (keyword). |
| **Node types** | Multi-select | `content` | Retrieve document **content**, parent **summary** nodes, or both. At least one required. |
| **Retrieve previous/next** | Optional group | Off | Also pull the chunks immediately before/after each hit, preserving surrounding context. |
| **Retrieve summaries** | Optional group | Off | Also pull parent-level summary nodes. |
### Output formatting
| Field | Type | Default | Description |
| ------------------ | --------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **Context prompt** | Long text | *(default combiner)* | Optional template controlling how the retrieved passages are formatted into the returned context block. Leave empty to use the default. |
## Best practices
**Reuse the knowledge base you already built.** The Retrieval Agent reads the same vector collections as the Document
Intelligence Assistant — point it at an existing, well-maintained knowledge base rather than standing up a new one.
**Match the embedding model to the index.** As with every retrieval agent, an embedding mismatch is the most common
cause of empty results.
**Prefer the Document Intelligence Assistant unless you specifically need raw context.** For nearly all chat-style
question answering, the full RAG agent is the right choice. Choose the Retrieval Agent only when answer generation
genuinely belongs somewhere else.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/7_document_navigation_assistant.md
title: "Document Navigation Assistant"
description: "Routes each question to the right knowledge sources, confirms the choice with the user, then hands off to a RAG agent."
---
# Document Navigation Assistant
The **Document Navigation Assistant** (the namespace-selection agent) is a **router that sits in front of a
[Document Intelligence Assistant](../5_document_intelligence_assistant/)**. When your organization has many separate
knowledge collections — HR, Legal, Engineering, Sales, and so on — searching all of them for every question produces
noisy, mixed answers. This agent first works out *which* collections a question belongs to, confirms that choice with
the user, and only then hands the question off to a RAG agent that searches just those collections.
It does not retrieve or answer questions itself. Its job is **navigation**: pick the right sources, then delegate.
::: tip When to reach for this agent
Use it when you have several distinct knowledge bases and a single RAG agent searching all of them would mix unrelated
material. If you only have one knowledge base, or a RAG agent searching everything works well enough, you don't need
this — just use the [Document Intelligence Assistant](../5_document_intelligence_assistant/) directly.
:::
## Knowledge bases and namespaces
To understand this agent you need two terms:
- A **knowledge base** (or *bucket*) is a top-level collection of documents — for example `company_policies`.
- A **namespace** is a labelled sub-collection within a knowledge base — for example `hr-policies`, `legal`, or
`benefits` inside `company_policies`.
The Document Navigation Assistant chooses **one namespace per knowledge base** that best matches the user's question,
and passes that selection to the RAG agent so its search is restricted to exactly those namespaces.
## What it does
```mermaid
flowchart LR
A[Question] --> B[Work out the right
knowledge sources]
B --> C{Confident
enough?}
C -- No --> D[Ask a
clarifying question]
D --> B
C -- Yes --> E[Confirm the
choice with the user]
E --> F[Hand off to the
RAG agent]
```
1. **Work out the right sources.** The agent looks up the namespaces available in the configured knowledge bases and
uses a language model to decide which ones the question is about.
2. **Ask if unsure.** If the question is too vague to choose confidently, the agent asks the user a clarifying question
(for example, "Are you asking about employee or customer policies?") and refines its choice from the answer. This can
repeat until it is confident.
3. **Confirm with the user.** Before searching, the agent shows the user the sources it intends to use and asks for
approval. If the user rejects them, it proposes a different selection.
4. **Hand off to the RAG agent.** Once approved, it delegates the question to a configured
[Document Intelligence Assistant](../5_document_intelligence_assistant/), telling it to search only the chosen
namespaces. The RAG agent's answer is passed straight back to the user.
The chosen sources are **remembered for the rest of the conversation**. Follow-up questions in the same chat skip the
selection and approval steps and go straight to the RAG agent with the same sources — so the user only navigates once.
::: tip Steps 2 and 3 are human-in-the-loop
The clarifying question and the approval step pause the workflow and wait for the user. This is what keeps routing
accurate and transparent: the user always sees and confirms which knowledge sources will be searched before any answer
is generated.
:::
## What it does *not* do
- **It doesn't retrieve or answer.** All searching and answering is done by the RAG agent it delegates to. This agent
only decides *where* to look and confirms it with the user.
- **It needs a RAG agent to delegate to.** On its own it produces no answers — a configured
[Document Intelligence Assistant](../5_document_intelligence_assistant/) profile is a hard requirement (see below).
## Typical scenarios
- **Enterprise with siloed knowledge.** Engineering, Sales, HR, and Legal each have their own documents; the assistant
routes each question to the right team's namespace instead of searching all of them.
- **Topic-specialized platforms.** A medical knowledge platform with `cardiology`, `neurology`, and `oncology`
namespaces asks which specialty applies before answering.
- **Reducing noise and cost.** Anywhere a "search everything" RAG agent returns mixed or contradictory passages,
narrowing the search to the right namespace produces cleaner answers and cheaper retrieval.
## Before you start: prerequisites
1. **A configured RAG agent profile.** This is the single most important prerequisite. The Document Navigation Assistant
delegates every answer to a [Document Intelligence Assistant](../5_document_intelligence_assistant/) profile, so that
profile must already exist and work. Set it up and test it first.
2. **Knowledge bases organized into namespaces.** There must be more than one namespace to choose between for routing to
be meaningful. Namespaces are defined when documents are ingested by the [data pipelines](../../6_pipelines/).
3. **A chat model** for the routing language model, available through your platform's LiteLLM configuration.
## Setting it up
The agent is delivered as a **blueprint** from which you create configured **profiles** — see
[Blueprints & Profiles](../2_blueprints_and_profiles/). With the prerequisites in place:
1. **Open the blueprint** under **Admin > Agents > Blueprints** and select **Document Navigation Assistant**.
2. **Create a profile** with an **Agent ID**, **Name**, **Description**, and **Icon**.
3. **Choose the chat model** used to work out which sources a question belongs to.
4. **Select the knowledge databases** the assistant is allowed to route between. Their namespaces become the choices it
picks from.
5. **Select the RAG agent to delegate to.** Pick the Document Intelligence Assistant profile that will do the actual
searching and answering.
6. **Optionally customize** the approval message shown to users and how much conversation history the router keeps.
7. **Save and test.** Ask questions that should land in different namespaces and confirm the routing and approval behave
as expected.
## Configuration reference
### Profile identity
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier. Lowercase letters, digits, underscores, hyphens. |
| **Name** | Text (per language) | Yes | Display name shown to users. |
| **Description** | Text (per language) | Yes | Short explanation shown in the assistant picker. |
| **Icon** | Icon picker | No | Visual identifier. |
### Routing
These settings control how the assistant decides where to search and where it sends the question.
| Field | Type | Default | Required | Description |
| ----------------------- | --------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Model** | Model picker | — | Yes | The chat model used to work out which namespaces a question belongs to. |
| **Knowledge Databases** | Knowledge-base picker | — | Yes | The knowledge bases the assistant may route between. Their namespaces are the options it chooses from. At least one. |
| **RAG Agent** | Agent picker | — | Yes | The [Document Intelligence Assistant](../5_document_intelligence_assistant/) profile that questions are delegated to. The picker lists agents that accept RAG-style requests. |
The **Model** picker also exposes the standard language-model parameters (temperature, timeout, and the log-probability
options) described on the [Document Intelligence Assistant](../5_document_intelligence_assistant/#language-model) page;
a low temperature is best, since routing should be consistent.
### Conversation behaviour
| Field | Type | Default | Description |
| ----------------------------- | --------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Max history entries** | Number | `20` | How many recent conversation turns the router keeps while working out the right sources. The original question is always kept. Minimum 4, maximum 100. |
| **Approval message template** | Long text | *(default provided)* | The message shown when asking the user to approve the chosen sources. Use the `{namespaces}` placeholder where the proposed list should appear. |
## Best practices
**Build and test the RAG agent first.** This assistant is only as good as the Document Intelligence Assistant it
delegates to. Get that working on its own before putting a navigator in front of it.
**Give namespaces clear, descriptive names.** The routing model decides based on namespace names and descriptions —
`hr-policies` and `customer-contracts` route far more reliably than `docs1` and `docs2`.
**Keep the routing model's temperature low.** Routing is a classification task; consistency matters more than
creativity.
**Use it only when you genuinely have multiple knowledge bases.** For a single knowledge base it adds an approval step
with no benefit — use the [Document Intelligence Assistant](../5_document_intelligence_assistant/) directly instead.
**Tune the approval message for your users.** The default asks for confirmation generically; a message phrased for your
audience makes the navigation step feel natural rather than technical.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/8_mcp_tool_agent.md
title: "MCP Tool Agent"
description: "Connects to an external tool server (MCP) and uses its tools to take actions, reasoning step by step until the task is done."
---
# MCP Tool Agent
The **MCP Tool Agent** (the MCP ReAct agent) is the one agent that *does things* rather than just answering questions.
It connects to an external **MCP server** — a standard way of exposing tools to AI — discovers the tools that server
offers, and then reasons step by step about which tools to call to fulfil the user's request: create a ticket, look up a
record, send a message, query an internal API, and so on.
Where the other agents read from documents or relay to humans, this agent **takes actions in other systems**. It works
in a loop — think, act, observe, think again — known as **ReAct** (Reason + Act), continuing until it has what it needs
to answer or until it reaches a safety limit.
::: tip What is MCP?
The **Model Context Protocol (MCP)** is an open standard for exposing tools and data to AI agents. Instead of building a
custom integration for every system, you point the agent at an MCP server and it automatically discovers what that
server can do. See the [Agents overview](../#connecting-to-external-tools) for the platform's take on connecting agents
to external tools.
:::
## What it does
```mermaid
flowchart LR
A[Question] --> B[Discover the
server's tools]
B --> C{Tool or
answer?}
C -- Call a tool --> D[Run the tool,
read the result]
D --> C
C -- Answer --> E[Reply to
the user]
```
1. **Discover the tools.** The agent connects to the configured MCP server, lists the tools (and any data resources) it
offers, and prepares the conversation — including any instructions the server itself provides.
2. **Decide what to do.** The language model looks at the request and the available tools and decides: call a tool, or
answer the user directly?
3. **Run the tool and observe.** If it chose a tool, the agent calls it on the MCP server, reads the result, and feeds
that back into the conversation.
4. **Repeat.** With the new information, the model decides again — call another tool or answer. This reason-act-observe
loop continues as needed.
5. **Answer or stop.** When the model is ready, it replies to the user. If it loops too many times without finishing, it
stops gracefully at a configured iteration limit.
Like the other chat agents, it also recalls memory, reads files the user attached and searches collections referenced
with `#`, and cites them as sources. See [Shared chat settings](../2_blueprints_and_profiles/#shared-chat-settings). To
let the model choose among tools other than an MCP server's, such as knowledge search, files and memory, see the
Universal Agent.
## What it does *not* do
- **No knowledge base / RAG of its own.** It has no configured collections to search; beyond the MCP tools it only reads
the files attached to a message and the collections referenced with `#`. For grounded answers over a fixed knowledge
base, use the [Document Intelligence Assistant](../5_document_intelligence_assistant/).
- **No human escalation.** It doesn't hand off to a colleague — its "hands" are the tools on the MCP server.
- **It's only as capable as the server it connects to.** The tools, their behaviour, and their permissions all live on
the MCP server, not in this agent. The agent discovers and orchestrates them; it doesn't define them.
## Typical scenarios
- **Ticketing actions.** Connected to a ticketing system's MCP server, it can create, look up, and update tickets in
response to a user's request.
- **Record lookups.** Query a CRM or internal database exposed via MCP and summarise what it finds.
- **Cross-system tasks.** Chain several tool calls — find a record, then act on it — within a single request.
- **Sending messages or notifications** through a system that exposes those actions as MCP tools.
## Before you start: prerequisites
1. **A reachable MCP server.** This is the central dependency. An MCP server must already be running and reachable from
the platform, exposing the tools you want the agent to use. The agent does not provide tools itself — it connects to
a server that does.
2. **The server's URL** and, if the server requires it, **credentials** (a static API key, or per-user authentication —
see the auth modes below).
3. **A chat model** capable of tool use, available through your LiteLLM configuration. Tool-calling quality depends
heavily on the model, so prefer a capable one.
::: warning Actions are real
Unlike the read-only agents, this agent can change things in other systems. Make sure the MCP server only exposes tools
you're comfortable letting the agent invoke, and scope its credentials accordingly.
:::
## Setting it up
The agent is delivered as a **blueprint** from which you create configured **profiles** — see
[Blueprints & Profiles](../2_blueprints_and_profiles/). With the prerequisites in place:
1. **Open the blueprint** under **Admin > Agents > Blueprints** and select **MCP Tool Agent**.
2. **Create a profile** with an **Agent ID**, **Name**, **Description**, and **Icon**.
3. **Configure the MCP connection.** Give it a name, the server URL, and choose how to authenticate (see auth modes
below).
4. **Choose the chat model.** Pick a capable, tool-calling model and keep the temperature low.
5. **Write a system prompt** describing the agent's role and any rules about how it should use the tools.
6. **Set the iteration limit** to bound how many reason-act loops it may run.
7. **Save and test** with a request that should trigger a tool call, and confirm the tool runs and the result comes
back.
## Configuration reference
### Profile identity
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier. Lowercase letters, digits, underscores, hyphens. |
| **Name** | Text (per language) | Yes | Display name. |
| **Description** | Text (per language) | Yes | Short explanation of what this profile does. |
| **Icon** | Icon picker | No | Visual identifier. Defaults to a plug icon. |
### MCP server connection
How the agent reaches the external tool server.
| Field | Type | Default | Description |
| ------------- | ---------------- | --------- | ------------------------------------------------------------------------ |
| **Name** | Text | — | A logical name for this connection. Required. |
| **URL** | Text | — | The MCP server's URL. The transport is inferred automatically. Required. |
| **Auth mode** | Choice | `api_key` | How to authenticate (see below). |
| **API key** | Password | *(empty)* | The static key, shown only when **Auth mode** is *API key*. |
| **Timeout** | Number (seconds) | `30` | How long to wait for the server before giving up. Range 1–300. |
**Auth modes:**
| Mode | What it does | When to use |
| -------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **None** | No credentials sent. | Open or network-trusted servers. |
| **API key** | Sends a single static key for all requests. | The agent acts as one shared service account. |
| **User token** | Forwards the requesting user's own token to the server. | When actions should be attributed to — and permitted per — the individual user, so the external system's audit trail and per-user permissions stay correct. |
### Reasoning
| Field | Type | Default | Description |
| -------------------- | ------------ | --------- | ------------------------------------------------------------------------------------------------------------- |
| **Model** | Model picker | — | The chat model that reasons and selects tools. Prefer a strong tool-calling model. Required. |
| **System Prompt** | Long text | *(empty)* | Instructions for how the agent should behave and use the tools. |
| **Max Iterations** | Number | `10` | How many reason-act loops the agent may run before stopping gracefully. Range 1–100. Bounds runaway tool use. |
| **Max Input Tokens** | Number | `128000` | The input budget; the conversation is trimmed to fit. Range 1,000–200,000. |
The **Model** also exposes the standard parameters (temperature, timeout, log-probability options) described on the
[Document Intelligence Assistant](../5_document_intelligence_assistant/#language-model) page.
## Best practices
**Constrain the server, not just the agent.** The strongest safety control is what the MCP server exposes. Give it only
the tools you want available, and scope its credentials to the minimum needed.
**Prefer user-token auth when actions are personal.** If the agent acts on a user's behalf in another system, *User
token* auth keeps that system's permissions and audit trail accurate, rather than everything appearing as one shared
account.
**Set a sensible iteration limit.** The default of 10 is fine for most tasks. Lower it for simple single-tool actions;
raise it cautiously for multi-step workflows. The limit is your guardrail against the agent looping indefinitely.
**Use a capable, tool-calling model at low temperature.** Reliable tool selection depends on the model. A strong model
at a low temperature picks the right tool with the right arguments far more consistently.
**Be explicit in the system prompt.** Tell the agent what it's for, which tools to prefer, and what it must not do.
Clear instructions reduce wrong or unnecessary tool calls.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/9_expert_coordinator_agent.md
title: "Expert Coordinator Agent"
description: "Relays a question to a human expert on Slack or Teams, checks the answer, and saves it as organizational knowledge."
---
# Expert Coordinator Agent
The **Expert Coordinator Agent** (the expert-asking agent) is the bridge between your AI agents and your human experts.
When a question can't be answered automatically, this agent posts it to a designated expert channel on **Microsoft Teams
or Slack**, waits for a person to reply, checks whether the reply actually answers the question (asking follow-ups if
not), and then saves the answer into **organization memory** so it can be reused automatically next time.
It is usually not something end users chat with directly. Instead it works behind the scenes — most often invoked by the
[Company Knowledge Agent](../10_company_knowledge_agent/) when that agent runs out of documented answers. Its value is
**knowledge capture**: every expert consultation turns a one-off human answer into reusable organizational knowledge.
## What it does
```mermaid
flowchart LR
A[Question to ask] --> B[Post to the
expert channel]
B --> C[A human replies
on Slack/Teams]
C --> D{Answer good
enough?}
D -- No --> E[Ask a
follow-up]
E --> B
D -- Yes --> F[Save to organization
memory & return]
```
1. **Post to the expert channel.** The agent sends the question to the configured Teams or Slack channel where your
experts are.
2. **Wait for a human reply.** A person answers in that channel, in their normal workflow — no special tool to learn.
3. **Check the answer.** A language model judges whether the reply genuinely answers the question or whether the expert
declined / answered only partially.
4. **Ask a follow-up if needed.** If the answer falls short, the agent composes a targeted follow-up question and posts
it back to the channel — repeating up to a configurable number of rounds.
5. **Save and return.** Once the answer is sufficient, the agent stores the question-and-answer pair in organization
memory and returns the answer to whoever asked (typically the Company Knowledge Agent, which relays it to the user).
::: tip This is "bot-in-the-loop"
Where other agents pause for the *end user* (human-in-the-loop), the Expert Coordinator pauses for a *different human* —
a subject-matter expert reached through a chat channel. The original user doesn't wait in the chat; they're told the
question was forwarded and will be answered when an expert replies.
:::
## Knowledge capture
The reason to use this agent rather than just emailing an expert is that **each answer is captured**. When an expert
answers once, the Q&A is written to organization memory and becomes searchable for everyone. The next time the same
topic comes up, the [Company Knowledge Agent](../10_company_knowledge_agent/) can answer it from that stored knowledge
without bothering the expert again — so the expert's effort compounds over time instead of being lost in a chat thread.
## Typical scenarios
- **Engineering escalation.** A Company Knowledge Agent can't find a documented answer about an internal system, so it
consults the engineering team's Teams channel; the answer is captured for future questions.
- **Specialist desk.** A small group of subject-matter experts fields the genuinely novel questions while the AI handles
everything already documented.
- **Building the knowledge base from conversations.** Over time, expert answers accumulate in organization memory,
steadily reducing how often a human needs to be involved.
## Before you start: prerequisites
1. **A connected Teams or Slack bot.** The agent reaches experts through a bot registered on your collaboration
platform. That bot connection must be set up first — see
[Slack & Teams integration setup](../../17_slack_teams_integrations/1_setup/). You'll need the channel and bot
identifiers from that setup to fill in the configuration below.
2. **An expert channel.** A Teams or Slack channel where your experts are present and willing to answer questions.
3. **A chat model** for checking answers and composing follow-ups, available through your LiteLLM configuration.
## Setting it up
The agent is delivered as a **blueprint** from which you create configured **profiles** — see
[Blueprints & Profiles](../2_blueprints_and_profiles/). With the prerequisites in place:
1. **Open the blueprint** under **Admin > Agents > Blueprints** and select **Expert Coordinator Agent**.
2. **Create a profile** with an **Agent ID**, **Name**, **Description**, and **Icon**.
3. **Choose the channel** (Teams or Slack) and fill in its identifiers (see the configuration reference).
4. **Set the organization-memory target** so captured answers are written where your knowledge agents can read them.
5. **Choose the chat model** and, if you like, adjust how many follow-up rounds are allowed.
6. **Save.** The profile can now be targeted by a Company Knowledge Agent (or invoked directly).
::: warning Channel configuration lives in the form, not in environment variables
In earlier versions the channel was set through environment variables (`EXPERT_ASKING_CHANNEL_TYPE`, `TEAMS_CHANNEL_ID`,
`SLACK_CHANNEL_ID`, …). That is no longer the case — **all channel settings are now part of the agent's configuration
form** and are edited per profile in the Admin UI. If you find references to those environment variables elsewhere,
treat them as outdated.
:::
## Configuration reference
### Profile identity
| Field | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------------------- |
| **Agent ID** | Text | Yes | Unique, URL-safe identifier. Lowercase letters, digits, underscores, hyphens. |
| **Name** | Text (per language) | Yes | Display name. |
| **Description** | Text (per language) | Yes | Short explanation of what this expert profile is for. |
| **Icon** | Icon picker | No | Visual identifier. |
### Expert channel
Pick the platform, then fill in that platform's fields. The identifiers come from your
[bot integration setup](../../17_slack_teams_integrations/1_setup/).
| Field | Type | Default | Description |
| ---------------- | ------ | ------- | -------------------------------------------------------- |
| **Channel Type** | Choice | `teams` | Which platform to use: **Microsoft Teams** or **Slack**. |
**When Channel Type is Microsoft Teams:**
| Field | Type | Required | Description |
| -------------- | ---- | -------- | ------------------------------------------------------- |
| **Channel ID** | Text | Yes | The Teams channel ID (format like `19:…@thread.tacv2`). |
| **Tenant ID** | Text | Yes | Your Azure AD tenant ID (a UUID). |
| **Bot ID** | Text | Yes | The Teams bot's application ID (a UUID). |
**When Channel Type is Slack:**
| Field | Type | Default | Description |
| --------------- | ---- | -------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| **Channel ID** | Text | — | The Slack channel ID (starts with `C`). |
| **Service URL** | Text | `https://slack.botframework.com` | Bot Framework service URL. Use the EU endpoint (`https://europe.slack.botframework.com`) for EU data residency. |
### Follow-up behaviour
| Field | Type | Default | Description |
| ------------- | ------ | ------- | ------------------------------------------------------------------------------------------------ |
| **Max loops** | Number | `3` | How many times the agent may ask a follow-up when the expert's answer is incomplete. Range 1–10. |
### Language model
| Field | Type | Default | Description |
| ---------------------------------------------------------------------- | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Model** | Model picker | — | The chat model used to judge answer sufficiency and write follow-up questions. Required. |
| **Temperature** | Number | `0.0` | Keep low — this is a judgement/extraction task, not a creative one. Range 0.0–2.0. |
| **Return Log Probabilities** / **Top Log Probabilities** / **Timeout** | — | — | Standard language-model options, as on the [Document Intelligence Assistant](../5_document_intelligence_assistant/#language-model) page. |
### Organization memory
Where captured expert answers are written. Read by knowledge agents (such as the Company Knowledge Agent) so answers are
reused automatically.
| Field | Type | Default | Description |
| ------------------------------ | --------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Tenant ID** | Text | platform default | Which tenant's shared memory to write to. |
| **Allowed namespaces** | List | *(empty)* | Allow-list of namespaces. Empty means unrestricted. Also validates the write target. |
| **Default namespace** | Text | platform default | The namespace answers are written to when a request doesn't specify one. Must be in the allow-list if one is set. |
| **Organization Memory Format** | Long text | `Question: {question}\n\nExpert Answer: {answer}` | Template for the stored snippet. Use `{question}` and `{answer}` placeholders. |
## Best practices
**Put real experts in the channel.** The agent is only as good as the people answering. Choose a channel where
knowledgeable colleagues are present and willing to help.
**Keep the namespace consistent with your knowledge agents.** For captured answers to be reused, write them to a
namespace that the [Company Knowledge Agent](../10_company_knowledge_agent/) (or a RAG agent) actually reads from.
**Tune the follow-up limit to your experts' patience.** Two or three rounds is usually enough; too many can feel like an
interrogation to the human answering.
**Pair it with a Company Knowledge Agent.** On its own this agent just relays questions. Its real power comes from being
the escalation target of a [Company Knowledge Agent](../10_company_knowledge_agent/), which only consults it when its
own documents fall short.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/10_company_knowledge_agent.md
title: "Company Knowledge Agent"
description: "A document-answering assistant that falls back to a human expert (with your permission) when the documents don't have the answer."
---
# Company Knowledge Agent
The **Company Knowledge Agent** (the expert-RAG agent) is the
[Document Intelligence Assistant](../5_document_intelligence_assistant/) with a human safety net. It answers questions
from your documents exactly like the Document Intelligence Assistant — and when it can't find a good enough answer,
instead of giving up it offers to **ask a human expert**. With the user's permission, it hands the question to an
[Expert Coordinator Agent](../9_expert_coordinator_agent/), which reaches a person on Slack or Teams, and then answers
the user using the expert's reply.
It combines two strengths: the speed and scale of document-based answers, and the reliability of a human for the
questions your documents don't cover yet. And because expert answers are captured into organization memory, the agent
gradually needs the human less often.
::: tip When to reach for this agent
Use it when document coverage is incomplete and a wrong or missing answer matters — you'd rather involve a human than
have the agent guess or say "I don't know." If your documents fully cover the topic, the plain
[Document Intelligence Assistant](../5_document_intelligence_assistant/) is simpler and sufficient.
:::
## What it does
It runs the full RAG workflow first; the expert path only opens when the retrieved context isn't enough to answer.
```mermaid
flowchart LR
A[Question] --> B[Try to answer
from documents]
B --> C{Enough
context?}
C -- Yes --> D[Answer with
citations]
C -- No --> E[Ask the user:
consult an expert?]
E -- Approved --> F[Expert Coordinator
asks a human]
F --> G[Answer using
the expert's reply]
```
1. **Try to answer from documents.** It performs the same retrieve → rerank → check-sufficiency flow as the
[Document Intelligence Assistant](../5_document_intelligence_assistant/). If the documents are sufficient, it answers
with citations and stops — identical to a normal RAG answer.
2. **Detect a gap.** If the context-sufficiency check decides the retrieved material isn't enough, the expert path
begins.
3. **Ask the user's permission.** The agent tells the user it doesn't have the answer and asks whether it may consult an
expert. Nothing is forwarded without consent. If the user declines, the agent simply responds with what it has.
4. **Consult an expert.** On approval, it delegates the question to the configured
[Expert Coordinator Agent](../9_expert_coordinator_agent/), which posts it to a human on Slack or Teams. The user is
told their question has been forwarded.
5. **Answer with the expert's reply.** When the expert answers, that reply is folded back in as context and the agent
composes the final answer. (The Expert Coordinator also stores the answer in organization memory, so next time the
agent can answer it directly.)
::: tip Two kinds of human involvement
The Company Knowledge Agent uses **human-in-the-loop** for the user's *consent* ("may I ask an expert?"), and the Expert
Coordinator it delegates to uses **bot-in-the-loop** to reach the *expert* on a chat channel. Together they keep the
escalation transparent: the user always approves before a colleague is contacted.
:::
## What it does *not* do
- **It doesn't contact experts directly.** All human contact happens through the
[Expert Coordinator Agent](../9_expert_coordinator_agent/) it delegates to — which must be configured separately.
- **It doesn't escalate without consent.** The user is always asked first; declining keeps the conversation purely
document-based.
- **It doesn't ingest documents.** Like all RAG agents, it reads what a [pipeline](../../6_pipelines/) has indexed.
## Before you start: prerequisites
This agent sits on top of two others, so both of their setups must be in place first:
1. **Everything the Document Intelligence Assistant needs.** A populated knowledge base, a matching embedding model, and
a chat model — see that agent's
[prerequisites](../5_document_intelligence_assistant/#before-you-start-prerequisites). The Company Knowledge Agent
shares all of the same retrieval configuration.
2. **A configured Expert Coordinator Agent profile.** The escalation target must already exist and work, including its
connected Teams/Slack bot. Set up and test the [Expert Coordinator Agent](../9_expert_coordinator_agent/) first — the
Company Knowledge Agent is useless without one to delegate to.
## Setting it up
The agent is delivered as a **blueprint** from which you create configured **profiles** — see
[Blueprints & Profiles](../2_blueprints_and_profiles/). With the prerequisites in place:
1. **Open the blueprint** under **Admin > Agents > Blueprints** and select **Company Knowledge Agent**.
2. **Create a profile** and configure it exactly like a
[Document Intelligence Assistant](../5_document_intelligence_assistant/): profile identity, chat model, at least one
knowledge source, and any reranking, guards, prompts, and memory you want.
3. **Turn on the context-sufficiency guard.** Expert escalation is triggered by the sufficiency check deciding the
context isn't enough — so this guard should be enabled, or the agent will not escalate.
4. **Select the Expert Agent.** Choose the [Expert Coordinator Agent](../9_expert_coordinator_agent/) profile to
escalate to. This is the one setting unique to this agent.
5. **Save and test** with a question your documents *can* answer (expect a normal cited answer) and one they *can't*
(expect the consent prompt and escalation).
## Configuration reference
The Company Knowledge Agent has **the entire configuration of the
[Document Intelligence Assistant](../5_document_intelligence_assistant/#configuration-reference)** — profile identity,
language model, knowledge sources, reranking, context-sufficiency guard, suitability guard, user memory, organization
memory, prompts, and input budget. Refer to that page for all of those fields; they behave identically here.
On top of that, it adds a single new setting for escalation:
### Expert escalation
| Field | Type | Required | Description |
| ---------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Expert Agent** | Agent picker | Yes | The [Expert Coordinator Agent](../9_expert_coordinator_agent/) profile to consult when the documents don't have a sufficient answer. The picker lists agents that can take expert questions. |
::: warning Enable the context-sufficiency guard
Escalation only happens when the
[context-sufficiency guard](../5_document_intelligence_assistant/#context-sufficiency-guard-optional) decides the
retrieved context isn't enough. If that guard is left off, the agent will almost always answer from documents alone and
never reach the expert path — defeating the purpose of this blueprint.
:::
## Best practices
**Get both underlying agents working first.** This is a composition of a RAG agent and an expert agent. Confirm your
Document Intelligence setup answers well, and your Expert Coordinator reaches a real human, before combining them here.
**Enable the sufficiency guard and tune it.** It's the trigger for escalation. Too lenient and the agent guesses instead
of asking; too strict and it escalates trivial questions. Test with real questions and adjust.
**Point escalation and memory at the same namespace.** For captured expert answers to come back as document-free answers
later, the Expert Coordinator must write to a namespace this agent's organization memory reads from. Keep them aligned
so the knowledge base genuinely grows from each consultation.
**Set expectations in the system prompt.** Since some answers will arrive only after a human replies, make sure the
assistant's wording (and your users' expectations) account for the "I've forwarded this to an expert" path.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/11_email_agent.md
title: "Email Agent"
description: "A mailbox agent that reads unread mail from an IMAP inbox, files it away, and drafts replies for a human to review — it never sends."
---
# Email Agent
The **Email Agent** (the IMAP agent) connects to a mailbox over IMAP and works through it on your behalf. It lists the
unread mail in an inbox, fetches a message with its attachments, optionally files that message into another folder, and
— as a separate, independently scheduled job — drafts replies to a batch of messages and leaves them in your Drafts
folder.
Unlike the chat-based assistants, this agent has **no chat interface**. You do not talk to it. It is configured once in
the Admin UI and then triggered programmatically — by a scheduler, by another workflow, or via the API. In this respect
it behaves like the [Retrieval Agent](../6_retrieval_agent/): it is a building block for automation rather than a
conversational partner.
::: tip When to reach for this agent
Use the Email Agent when work arrives by email and you want the routine part handled automatically — triaging a shared
mailbox, filing processed mail out of the inbox, or preparing reply drafts overnight so a person only has to review and
click send. If you want a colleague-style assistant to chat with, use the
[Instructed Assistant](../3_instructed_assistant/) or the
[Document Intelligence Assistant](../5_document_intelligence_assistant/) instead.
:::
::: warning It never sends email
The agent speaks IMAP only — there is no SMTP anywhere in it. It can read, move, and *save a draft*, but it can never
put a message on the wire. A human always opens the draft, reviews it, and sends it. This is a deliberate design
boundary, not a configuration default you can turn off.
:::
## What it does
The agent has **two independent capabilities**, each started by its own trigger. They do not depend on each other, and
you can schedule them separately — for example, read/move every five minutes and drafting once an hour.
```mermaid
flowchart TD
subgraph read [Read and file]
A[Read trigger] --> B[List unread mail]
B --> C{Any unread
messages?}
C -- No --> D[Stop]
C -- Yes --> E[Fetch oldest message
+ attachments]
E --> F{Move enabled?}
F -- No --> G[Leave in inbox, stop]
F -- Yes --> H[Move to processed folder]
end
subgraph draft [Draft replies]
I[Draft trigger] --> J{Drafting
enabled?}
J -- No --> K[Stop]
J -- Yes --> L[Read batch of
not-yet-drafted mail]
L --> M[Draft a reply
with the LLM]
M --> N[Append to Drafts,
mark source as drafted]
end
```
### Reading and filing
1. **List unread mail.** The agent opens the configured inbox folder and returns a summary of every unread message,
**oldest sent first**, up to the **Max Unread Messages** cap.
2. **Fetch a message.** The oldest unread message is fetched in full — sender, subject, date, body, and attachments. If
the inbox has no unread mail, the run simply stops here. Attachment bytes are written to the platform's file storage
and the message event carries only *references* to them, so the audit trail and the event stream stay small.
3. **File it away.** If **Move Fetched Mail** is enabled, the message is moved into the processed folder. If it is
disabled, the message stays where it is and the run ends.
The whole read path is **read-only at the protocol level**: the mailbox is opened with a read-only select and bodies are
fetched with `BODY.PEEK`, so the message is never marked as read behind your back. Moving relocates a message; it never
deletes one.
### Drafting replies
1. **Find candidates.** The drafter reads up to **Draft Batch Size** messages from its own source folder that have not
been drafted yet, **taking the oldest-sent ones first** — so a backlog drains in the order it arrived and the batch
size never leaves the oldest mail waiting behind newer arrivals. Already-drafted messages are recognised by an IMAP
flag the agent sets, so re-running the job does not produce duplicate drafts.
2. **Draft each reply.** For every message, the configured chat model is given your **Draft Prompt** and the original
message, and writes a reply body. The result is wrapped in a properly threaded reply envelope, so the draft shows up
in the right conversation in your mail client.
3. **Save and mark.** Each draft is appended to the drafts folder, and only then is the source message flagged as
drafted. The source message stays **unread** — a person still sees it as new mail waiting for attention.
::: details Which flag marks a message as drafted
The agent prefers a private keyword (`$AiHubDrafted`) that only it understands, so a message with an unsent draft is not
made to look like something it is not. Not every mail server supports custom keywords, though. On a server that does
not, the agent falls back to the standard `\Answered` flag — and those messages will show up as **replied** in your mail
client even though nothing has been sent yet. If your mailbox shows unexpected "replied" markers, this is why.
:::
::: details Why the source message stays unread
Drafting is a preparation step, not a resolution. The point is that a human opens the mailbox, sees the message as
unread and unhandled, finds a draft reply already waiting, and decides what to do. Marking mail as read would hide work
that has not actually been done.
:::
::: details What happens if a run is interrupted
The draft is appended to the drafts folder *before* the source message is flagged. If the run dies between those two
operations, the worst case is a duplicate draft on the next run — never a message that is silently marked as handled
without a draft existing. Losing work is treated as worse than doing a little of it twice.
:::
## What it does *not* do
- **It never sends.** No SMTP, no outbound delivery, no "send if confident" mode.
- **It never deletes.** Moving a message relocates it; nothing is permanently removed from the mailbox.
- **It has no chat interface.** It does not appear in the chat UI and cannot be asked questions.
- **It has no knowledge base.** Drafts are written from the original message plus your prompt — the agent does not
search your documents. For grounded, cited answers, use the
[Document Intelligence Assistant](../5_document_intelligence_assistant/).
- **It reads one message per read run.** The read/move chain lists all unread mail but fetches and files the oldest
message only. Schedule it more frequently to work through a backlog; the drafting chain is the one that processes a
batch.
## Configuration
Create a profile from the **IMAP Agent** blueprint in the Admin UI. The form has two sections: the mailbox connection
and the drafting settings.
### Mailbox connection
| Field | Type | Default | Description |
| ----------------------- | -------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **IMAP Host** | Text | — | Hostname of the IMAP server, e.g. `imap.example.com`. Required. |
| **IMAP Port** | Number | `993` | Server port. `993` is the standard for implicit TLS. Range 1–65535. |
| **Authentication** | Choice | Username and password | **Username and password**, or **Microsoft 365 (OAuth 2.0)** for Microsoft 365 mailboxes where basic authentication is disabled. Decides which of the credential fields below are shown. |
| **Username** | Text | — | Mailbox login, usually the full email address. With Microsoft 365 (OAuth 2.0), the address of the mailbox to process. Required. |
| **Password** | Password | *(empty)* | Mailbox password or, preferably, an app-specific token. Stored with the agent profile. Only shown for username and password. |
| **Tenant ID** | Text | *(empty)* | Directory (tenant) ID of your Microsoft Entra ID tenant. Only shown — and required — for Microsoft 365 (OAuth 2.0). |
| **Client ID** | Text | *(empty)* | Application (client) ID of the Entra ID app registration. Only shown — and required — for Microsoft 365 (OAuth 2.0). |
| **Client Secret** | Password | *(empty)* | Client secret of the Entra ID app registration. Stored with the agent profile. Only shown — and required — for Microsoft 365 (OAuth 2.0). |
| **Use TLS** | Toggle | On | Connect over implicit TLS. Turn off only for a plaintext test server. |
| **Inbox Folder** | Text | `INBOX` | The folder incoming mail is read from. |
| **Max Unread Messages** | Number | `50` | How many unread summaries a single run lists. Keeps the run small when the inbox is overflowing. 1–500. |
| **Move Fetched Mail** | Toggle | Off | When on, the fetched message is moved to the processed folder. When off, the move step is skipped. |
| **Processed Folder** | Text | `Processed` | Where a processed message is filed. Only shown — and required — when **Move Fetched Mail** is on. |
### Microsoft 365 with OAuth 2.0
Microsoft 365 tenants increasingly have basic authentication switched off, and then no username and password — not even
an app password — will log in over IMAP. For those mailboxes choose **Microsoft 365 (OAuth 2.0)**. The agent then
fetches an app-only access token from Microsoft Entra ID with the client credentials flow and logs in with it (SASL
`XOAUTH2`). No user has to sign in interactively, which is what a scheduled agent needs.
Setting it up is split between two roles. A Microsoft 365 administrator prepares the tenant once and hands over four
values; whoever configures the agent then enters them on the profile. The person configuring the agent needs neither
admin rights in Microsoft 365 nor PowerShell.
#### For the Microsoft 365 administrator (once)
Done once per tenant, plus step 4 for every further mailbox. Steps 1 and 2 happen in the Microsoft Entra admin center.
Steps 3 to 5 need Exchange Online PowerShell, because Exchange has no admin-center page for granting an app access to a
mailbox. Every step is required; a missing one fails the login.
1. **Register an application** in the Microsoft Entra admin center (**App registrations → New registration**, accounts
in this organizational directory only). Note the **Directory (tenant) ID** and **Application (client) ID** from its
overview page, and create a client secret under **Certificates & secrets** — copy its **Value**, not its Secret ID;
the value is shown only once.
2. **Add the IMAP permission.** Under **API permissions → Add a permission → APIs my organization uses**, pick **Office
365 Exchange Online → Application permissions → `IMAP.AccessAsApp`**, then **Grant admin consent**.
3. **Register the service principal in Exchange Online.** Connect with the `ExchangeOnlineManagement` module first:
```powershell
Connect-ExchangeOnline -UserPrincipalName
```
If PowerShell reports that the module could not be loaded, the execution policy is blocking it;
`Set-ExecutionPolicy Bypass -Scope Process` lifts that for the current window only. Then register the app:
```powershell
New-ServicePrincipal -AppId -ObjectId
```
The object ID is the one on the **Enterprise applications** overview page of the app, **not** the one on its App
registrations page. The wrong one is the most common cause of a failed login.
4. **Grant the service principal access to each mailbox** the agent processes:
```powershell
Add-MailboxPermission -Identity "support@contoso.com" -User -AccessRights FullAccess
```
`Get-ServicePrincipal | Format-List` shows the service principal's identity.
5. **Make sure IMAP is enabled on those mailboxes**:
```powershell
Set-CASMailbox -Identity "support@contoso.com" -ImapEnabled $true
```
6. **Hand over four values** to the person configuring the agent, over a channel you would trust with a password: the
tenant ID, the client ID, the client secret value, and the address of each mailbox granted in step 4. Tell them when
the secret expires, too.
::: warning Step 4 is the access boundary
The app can open exactly the mailboxes granted in step 4 and no others. Grant only the mailboxes the agent is meant to
process, and treat anyone who can run `Add-MailboxPermission` — or edit the agent profile's **Username** — as able to
point the agent at any mailbox that has been granted. The client secret carries the same weight as a mailbox password:
anyone holding it can read every granted mailbox.
:::
#### For the person configuring the agent
You need the four values from your Microsoft 365 administrator. On the agent profile, set:
| Field | Value |
| ------------------ | ------------------------------------------- |
| **IMAP Host** | `outlook.office365.com` |
| **IMAP Port** | `993` |
| **Use TLS** | On |
| **Authentication** | **Microsoft 365 (OAuth 2.0)** |
| **Username** | A mailbox address the administrator granted |
| **Tenant ID** | The tenant ID you were given |
| **Client ID** | The client ID you were given |
| **Client Secret** | The client secret value you were given |
A shared mailbox works as **Username**. A mailbox the administrator did not grant cannot be opened, however correct the
other values are — ask for it to be granted instead of changing anything else.
::: tip Client secrets expire
Entra ID client secrets are issued for at most two years, and an expired secret fails every run with `AADSTS7000222`.
Put the expiry date in your calendar: before it runs out, the administrator creates a new secret and you paste it into
the profile.
:::
#### When the login fails
The error in the run's event timeline tells you which side refused, and so who can fix it:
| Error | Cause | Fixed by |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `... these fields are empty: ...` | **Tenant ID**, **Client ID** or **Client Secret** is not filled in on the profile. | The person configuring the agent. |
| `AADSTS90002` / `AADSTS700016` | The tenant ID is wrong, or the client ID does not belong to an app in that tenant. | The person configuring the agent, by comparing with the values handed over. |
| `AADSTS7000215` / `AADSTS7000222` | The client secret is wrong — often the Secret ID pasted instead of the value — or it has expired. | Wrong: the person configuring the agent. Expired: the administrator (new secret). |
| `NO AUTHENTICATE failed` | Entra issued a token but Exchange refused it — step 3, 4 or 5 is missing, step 3 used the wrong object ID, or **Username** is not a granted mailbox. | The administrator, steps 3 to 5. |
Exchange answers `NO AUTHENTICATE failed` for all of its causes alike. Check first that **Username** is exactly a
mailbox that was granted; if it is, the administrator goes through steps 3 to 5 in order.
### Draft email settings
Everything in this section is hidden until **Draft Reply** is switched on.
| Field | Type | Default | Description |
| ----------------------- | ------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Draft Reply** | Toggle | Off | Master switch for the drafting capability. When off, a draft run stops immediately without doing anything. |
| **Draft Source Folder** | Text | `INBOX` | Where the drafter looks for candidate messages. Point it at your processed folder to draft replies to mail the move step filed. |
| **Draft Batch Size** | Number | `5` | How many messages are drafted per run. Must be at least 1. |
| **Drafts Folder** | Text | `Drafts` | Where drafts are saved. If the server does not have a folder by this name, its standard `\Drafts` folder is used instead. |
| **LLM Model** | Model picker | *(empty)* | The chat model that writes the reply body. Options come from your LiteLLM configuration. |
| **Draft Prompt** | Long text | *(a sensible default)* | Instructions steering tone, language, and format of the reply. The default asks for a concise, polite reply in the sender's language, with no invented facts and no subject line or signature. |
::: details Deployment-fixed limits you will not see in the form
Three size caps are fixed by whoever operates the platform and are not exposed as form fields, because they protect the
platform rather than shape the agent's behaviour:
| Limit | Default | What it protects |
| ------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Max message size | 50 MB | The raw size of a message is checked *before* the body is downloaded, so an oversized or hostile mail is refused rather than pulled into memory. |
| Max body size | 1 MB | Caps the message body carried in an event; longer bodies are truncated so the stored and streamed event stays within platform message-size limits. |
| Max attachment size | 10 MB | Caps a single stored attachment; larger attachments are skipped so one message cannot overload the attachment store. |
Ask your platform administrator if a legitimate workload runs into one of these.
:::
::: details How the settings combine at runtime
A **read** trigger connects with the mailbox settings, lists up to **Max Unread Messages** from **Inbox Folder**,
fetches the oldest one, and — if **Move Fetched Mail** is on — moves it to **Processed Folder**.
A **draft** trigger is independent. If **Draft Reply** is on, it reads up to **Draft Batch Size** not-yet-drafted
messages from **Draft Source Folder**, calls the selected **LLM Model** once per message with your **Draft Prompt**,
appends each result to **Drafts Folder**, and flags the source. The mail connection is opened only for the IMAP work and
closed again while the model is writing, so a slow model does not leave an idle connection for the server to drop.
:::
## Example workflows
**Triage a shared mailbox.** Enable **Move Fetched Mail** with a `Processed` folder and schedule the read trigger every
few minutes. Each run takes the oldest unread message out of the inbox and files it, with the full message and its
attachments recorded in the platform's audit trail. The inbox becomes a queue that drains itself.
**Prepare overnight reply drafts.** Leave moving off, enable **Draft Reply** with **Draft Source Folder** set to
`INBOX`, and schedule the draft trigger to run outside business hours with a batch size that matches your daily volume.
In the morning the team opens the mailbox and finds a draft waiting under each new message — review, adjust, send.
**Both, in sequence.** Point the read trigger at `INBOX` with moving on, and set **Draft Source Folder** to the same
`Processed` folder. Mail is filed as it arrives, and the drafter works through the filed messages on its own schedule.
The two jobs never contend for the same message.
## Best practices
**Use an app-specific password — or OAuth 2.0 on Microsoft 365.** Most providers (Gmail and others) issue
per-application credentials that can be revoked on their own. Use one instead of the account's real password, and give
the agent a mailbox that holds only what it needs to see. On Microsoft 365, use
[OAuth 2.0](#microsoft-365-with-oauth-2-0) instead: app passwords stop working once basic authentication is disabled.
**Start with moving and drafting off.** Both are off by default for a reason. Run the agent read-only first, confirm in
the event timeline that it connects and picks up the right messages, then switch on one capability at a time.
**Set the processed folder before enabling the move.** Turning on **Move Fetched Mail** with an empty **Processed
Folder** fails the run. Create the folder on the mail server first — the agent files into it, it does not create it.
**Keep the batch size small at first.** Every message in a batch costs one model call. Start at three to five, watch the
cost and quality in the traces, and raise it once you trust the output.
**Write the draft prompt for a reviewer, not for a recipient.** The best drafts are the ones a person can approve in
seconds. Ask for a short reply, an explicit statement when information is missing, and no invented facts — a draft that
politely says "I need to check X" is more useful than a confident wrong answer.
**Match the source folder to your schedule.** If the same folder feeds both the move step and the drafter, make sure the
read trigger runs often enough to keep the drafter supplied — and that the drafter's batch size is not so large it
repeatedly finds nothing to do.
**Remember that inbound mail is untrusted.** Anyone can send your mailbox anything, and the body of that message goes
into the model's prompt. The platform's PII guard anonymises personal data at the LLM gateway, but you should still
treat drafts as suggestions from an untrusted input — which is exactly why a human reviews every one before it is sent.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/12_email_classification_agent.md
title: "Email Processing Agent"
description: "A mailbox agent that reads every unread message, decides which category it belongs to, and files it into that category's folder — with a fallback folder for anything it is unsure about."
---
# Email Processing Agent
The **Email Processing Agent** turns a shared mailbox into a queue that sorts itself. On each run it reads every unread
message in the inbox, decides which of your categories it belongs to, and moves it into that category's folder. Anything
no category fits goes to a fallback folder rather than being guessed into a bucket.
Like the [Email Agent](../11_email_agent/), it has **no chat interface**. You configure it once in the Admin UI and
trigger it programmatically — by another workflow, or via the API.
::: warning There is no built-in schedule yet
The agent has no recurring trigger you can set in the Admin UI. Every run has to be started from outside, so "triages
itself" means "triages itself each time something asks it to". Until scheduling lands, drive it from whatever already
runs on a timer in your environment.
:::
::: warning It never sends email
The agent speaks IMAP only — there is no SMTP anywhere in it. It reads, it files, it creates folders. It cannot put a
message on the wire. This is a design boundary, not a setting.
:::
::: tip Categories are yours, not ours
There is no built-in taxonomy. You define the categories, and you can add or rename one at any time without a
deployment. What makes classification work is the **description** you write for each — see below.
:::
## What it does
```mermaid
flowchart TD
A[Trigger] --> B[List every unread message]
B --> C{Any unread?}
C -- No --> D[Report an empty run, stop]
C -- Yes --> E[Fetch each message
+ archive the original]
E --> F[Ask the model which
category each belongs to]
F --> G{Does a category fit?}
G -- Yes --> H[File into the
category's folder]
G -- No --> I[File into the
fallback folder]
H --> J[Report how many
were filed, per category]
I --> J
J --> K{Does the category
ask for a reply?}
K -- No --> L[Done]
K -- Yes --> M[Draft a reply and leave it
in the Drafts folder]
M --> L
```
1. **List.** Every unread message in the inbox, oldest sent first, up to **Max Unread Messages**.
2. **Fetch and archive.** Each message is fetched in full. Its attachments and the original message are written to the
platform's file storage, so the complete mail is preserved even after it has been moved.
3. **Classify.** The configured model is shown your category names and descriptions and picks one per message — or
declines, if none fits.
4. **File.** Each message is moved into the folder for its category. **If the folder does not exist, the agent creates
it** and subscribes it, so it shows up in your mail client.
5. **Report.** The run records how many messages were filed and how many landed in each category.
::: details Why re-running is safe
Filing is what prevents double work. Every message — categorised or not — leaves the inbox, so the next run's unread
listing simply cannot see it again. There is no flag to get out of sync and nothing to clean up. If a run fails
half-way, the messages it already filed stay filed and the rest are still sitting unread, ready for the next run.
:::
::: details The message stays unread
Mail is read with `BODY.PEEK`, so the agent never marks anything as seen. A person opening the `Support` folder still
sees genuinely unread mail waiting for them — the agent sorted it, it did not handle it.
:::
## Configuration
Create a profile from the **Email Processing Agent** blueprint in the Admin UI.
### Mailbox connection
The same fields as the [Email Agent](../11_email_agent/#mailbox-connection): host, port, authentication, username and
password or Entra ID credentials, TLS, inbox folder, and **Max Unread Messages**. There is no "processed folder" here —
the classifier decides where each message goes.
For a Microsoft 365 mailbox with basic authentication disabled, choose **Microsoft 365 (OAuth 2.0)** under
**Authentication**. The tenant needs a one-time setup by an administrator — an app registration, the `IMAP.AccessAsApp`
permission, and access granted per mailbox — described step by step in
[Microsoft 365 with OAuth 2.0](../11_email_agent/#microsoft-365-with-oauth-2-0).
### Categories
A repeating list. Add one entry per category:
| Field | Description |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| **Category** | A short name, e.g. `support_request`. Must be unique. |
| **Target Folder** | Where mail in this category is filed. Created automatically if it does not exist. Must be unique. |
| **What Belongs Here** | A description of the kind of mail that belongs in this category. **This is what the model reads.** |
| **Draft a Reply** | When on, mail filed here also gets a drafted reply left in your Drafts folder. Off by default. |
::: tip Write the description for a new colleague, not for a search engine
This field does the real work. Folder names alone cannot separate an *information request* from a *support request* —
but a description can: *"we can resolve this by providing information"* versus *"this requires an action from our
team"*. Describe the sender's **intent** and what handling the mail would involve. Listing keywords works far worse than
one clear sentence about what the category is for.
:::
::: warning Nested folder names use *your server's* separator
A target folder like `Triage/Support` builds a real folder tree only on servers whose hierarchy separator is `/` — Gmail
among them. On a server that uses `.` you would get one flat folder literally named `Triage/Support`; write
`Triage.Support` there instead. Mail is filed correctly either way, but only the matching separator gives you a tree. If
you are unsure, use flat names like `Support` and `Invoices`.
:::
### Classifier
| Field | Default | Description |
| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------- |
| **Fallback Folder** | `Uncategorised` | Where mail the model is unsure about goes. Never guessed into a category, never left in the inbox. |
| **Classification Model** | *(empty)* | The model that classifies. Leave empty to use the agent's main model. |
| **Classification Prompt** | *(a sensible default)* | Instructions steering how the model chooses. |
::: details How a message ends up in the fallback folder
The model is given one way out: it can say outright that **none of the categories fit**. When it does, the message goes
to the fallback folder instead of a category folder.
An earlier version also asked the model to rate its own confidence and diverted anything below a threshold. That setting
was removed. A self-reported score is written in the same breath as the answer rather than measured, so it adds no
information the choice does not already carry — measured across the platform's chat models on a deliberately ambiguous
message, the explicit decline caught it four times out of five while the threshold never once fired, and the one model
that misfiled did so at 0.95 confidence.
The practical consequence: **your category descriptions are the safety net, not a dial.** If mail is landing in the
wrong folder, sharpen the descriptions of the two categories being confused.
:::
### Schedule
The agent can run itself. Enable **Schedule** on the profile and give it the five cron positions plus a timezone; leave
it disabled and the agent only runs when something triggers it.
| Field | Example | Description |
| ---------------- | --------------- | ------------------------------------------- |
| **Minute** | `0` | `0-59`. `*/15` means every fifteen minutes. |
| **Hour** | `*` | `0-23`. |
| **Day of month** | `*` | `1-31`. |
| **Month** | `*` | `1-12`. |
| **Day of week** | `*` | `0-6`, Sunday is `0`. |
| **Timezone** | `Europe/Zurich` | The zone the positions are read in. |
The timezone is what makes "every day at 08:00" mean the same thing all year: occurrences are worked out in that zone,
so a daily schedule keeps its wall-clock time across daylight-saving changes.
Every position has to be filled in. A blank or malformed one is rejected when you save, rather than being stored and
quietly breaking the profile.
::: tip Start hourly, then tighten
`0 * * * *` — on the hour — is a good first schedule. It gives you a run an hour to inspect before you decide whether
the mailbox needs draining more often.
:::
::: details What happens when a run overlaps the next occurrence
A large or slow mailbox can still be filing when the next scheduled time comes around. Only one run at a time may hold a
mailbox: the second one reports that a previous run is still filing and stops without touching any mail. The occurrence
is skipped rather than queued, and the next one runs normally.
This matters because a message is still unread right up until it moves. Without the guard, two overlapping runs would
both read the same mail, both pay to classify it, and both try to file it.
A run keeps renewing its claim for as long as it is working, so how long a batch takes never causes it to lose the
mailbox — the ten-minute expiry only starts counting down once the run has actually stopped. If a run fails outright,
the claim lapses on its own within ten minutes and the next scheduled run proceeds, so a crash costs you at most one
occurrence.
In the rare case where a run does lose its claim mid-flight — a mailbox or a model stalled for longer than ten minutes
in a single step — it stops before filing anything rather than filing mail another run may already be moving. That shows
up as a failed run in tracing, and it is worth investigating: it means something took far longer than it should.
:::
::: warning Three things to know about scheduled runs
- **They are invisible in the chat UI.** A scheduled run belongs to no user, so it does not appear in anyone's
conversation list. Watch them in tracing instead. Choosing who sees them is planned separately.
- **They are not metered.** Usage limits are enforced on requests that arrive over HTTP, and a scheduled run does not
make one. A very frequent schedule against a busy mailbox can consume a lot of model budget unnoticed — the **Max
Messages** cap bounds a single run, and the schedule bounds how often that happens.
- **The agent has to be running.** If the agent is offline when a scheduled time passes, that occurrence is skipped, not
queued. Nothing is lost: the mail is still unread and the next run that does fire picks it up.
:::
### Reply drafting
Turn on **Draft Reply** to have the agent write a reply for the categories that warrant one, and leave it in your
**Drafts** folder for a person to read, edit and send. Nothing is ever sent by the agent — there is no mechanism in the
platform for it to send mail at all.
Drafting is chosen **per category**, on the **Draft a Reply** switch in the category list. That is the point of the
feature: a *complaint* usually deserves a reply, a *thank-you* rarely does, and an *invoice* wants paying rather than
answering. Mail that fitted no category and went to the fallback folder is never drafted — if the model could not say
what a message was about, it is in no position to answer it.
| Field | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Draft Reply** | The master switch for this section. Off by default. |
| **Drafts Folder** | Where drafts are appended. If the name is not found, your server's own Drafts folder is used, and failing that the name is created. |
| **LLM Model** | The model that writes the reply. Leave empty to use the agent's main model. |
| **Draft Prompt** | Instructions for tone and style. The shipped default is concise, polite, and forbids inventing facts. |
| **Draft Input Tokens** | How much of the message the model may be shown. See *Long mail* below. |
Each draft is threaded to the message it answers — `Re:` subject, correct `In-Reply-To` and `References` headers — so it
appears inside the original conversation in your mail client rather than as a stray new message.
::: warning A draft is a first pass, not an answer
Read every draft before sending it. Even a draft grounded in your own documents (below) is only as good as the documents
behind it, and an ungrounded one is written from the message alone — it has no access to your systems, your prices or
your case history, and it will not know what it does not know.
:::
#### Grounding drafts in your own documents
A reply written from the incoming message alone can acknowledge it, but it cannot *answer* it. Pick a **Knowledge
Agent** and drafts are written from your documents instead — the agent asks that knowledge agent to answer each message,
and uses the answer as the draft. Leave the Knowledge Agent off and every draft is written from the message alone, with
no retrieval at all.
With a knowledge agent picked, every drafted reply is answered from it. By default a category searches every collection
the knowledge agent retrieves from. **Select Knowledge Collections** appears on a category once its **Draft a Reply**
switch is on — collections only shape a drafted reply. Tick it and pick one or more collections, and its replies are
answered from those and nothing else — so a message classified as `support_request` can be answered from your support
material alone, with the category verdict making the lookup precise. Only the collections the knowledge agent you picked
is actually configured for are offered, because a collection outside its scope would retrieve nothing at all.
**The knowledge base layout this needs.** A collection is a top-level folder in your knowledge database — ingestion
creates one collection per folder automatically. So the setup is: one folder per category, holding the documents that
answer that category.
```text
support-kb/ ← knowledge database
├── support/ ← collection, for the support_request category
│ ├── troubleshooting-guide.pdf
│ └── known-issues.md
└── information/ ← collection, for the information_request category
├── price-list.pdf
└── opening-hours.md
```
Configure it in two places:
| Field | Where | What it is |
| -------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **Knowledge Agent** | Knowledge delegation | The agent that answers your mail from your documents. Leaving it off turns grounding off for the whole profile. |
| **Select Knowledge Collections** | On each category | Tick it to answer that category's replies from the collections you pick. Unticked, they are answered from all the agent's collections. |
A category pointed at a collection your knowledge base no longer holds fails the run **before** any mail is classified,
rather than quietly answering from nothing. So does one whose selection is switched on but empty — switch it off instead
to search all collections. A selection on a category whose **Draft a Reply** switch was later turned off is kept, hidden
and ignored until drafting is switched back on.
::: warning Upgrading a profile configured before this release
Earlier releases named one collection per category in a plain text field, with the databases listed once on the
classification section. Both fields were replaced by the per-category picker. A profile saved in the old shape is
carried over when the API starts — each category's old collection name is paired with the databases that were configured
— so grounding keeps working without you re-entering it. Categories that named no collection now search all of the
knowledge agent's collections. The conversion runs once when the API starts, and a category whose selection you later
switch off stays on all collections. Open the profile and check the picker once after upgrading: a carried-over
collection the knowledge agent you picked is not configured for is listed as unavailable, and that category needs a new
selection.
:::
**Grounded drafts are written in the sender's language.** The agent detects the language of each incoming message
(German, English, French or Italian) and has the knowledge agent answer in that language, so the draft is written in it;
scheduled runs have no user locale of their own, so without this every grounded draft would come out in German.
Detection is a model call and can misjudge mixed-language mail, for instance an English reply above a quoted German
thread. When the language cannot be determined, the previous behaviour applies.
**Every message still gets a draft.** When the lookup finds nothing that answers a message, the agent does not ask the
model to write around an empty result — an ungrounded reply that reads like a grounded one is worse than an honest
blank, because a reviewer skims it and sends it. Instead you get a draft carrying a fixed text you configure:
| Field | Used when |
| -------------------------------- | ------------------------------------------------------------------------------------- |
| **Draft When Nothing Was Found** | Retrieval ran and found nothing relevant. A true statement about your knowledge base. |
| **Draft When The Lookup Failed** | The lookup itself broke or never answered. A statement about your deployment. |
| **Knowledge Lookup Timeout** | How long to wait before treating a lookup as failed. Defaults to 10 minutes. |
The two are kept apart deliberately: reporting an outage as an absence of knowledge is how a broken deployment gets read
as a working one. And the reason there is always *some* draft is filing — a message that got no draft would be filed,
unflagged and never looked at again, sitting in neither the drafted nor the untouched state.
#### Attachments
Turn on **Read Attachments** to let the drafter use what is *inside* the attached files, not just the message body. PDFs
and images are read by the platform's document parser (with OCR); Word, PowerPoint and Excel files are converted
directly. Anything else — archives, audio — is skipped.
Reading an attachment costs a parsing round trip, so three limits keep it bounded:
| Field | Description |
| ------------------------------- | ---------------------------------------------------------------------- |
| **Max Attachments Per Message** | How many files are read per message, largest first. Default 3. |
| **Minimum Attachment Size** | Files smaller than this are skipped entirely. Default 8 KB. |
| **Attachment Character Limit** | How much text is kept from any single file. Default 20 000 characters. |
The size floor is there for a specific and very common case: the logo in an email signature arrives as an attachment
just like a real document does. Without the floor, every ordinary business mail would pay to have its signature image
parsed for nothing.
**A file the parser finds no text in is still mentioned to the model** — a photo, or a scan the OCR could not read. The
model is told the file arrived and that no text could be read from it, so the draft can acknowledge *"thank you for the
photo"* without inventing what the photo showed. It is never silently dropped: a sender who wrote "see attached"
deserves a reply that at least notices the attachment.
::: tip The agent does not describe images
Attachment reading extracts **text**. A photo with no writing in it contributes its name and type, nothing more — the
agent has no image understanding and will not tell you what is in a picture.
:::
#### Long mail
A long forwarded thread with a 200-page PDF attached will not fit any model's input limit. Rather than fail, the agent
trims — in a fixed order, so you can predict what the model saw:
1. The headers and the list of attachments are always kept.
2. Attachment text is dropped first, smallest file first.
3. Only then is the message body shortened, at a sentence boundary, and marked as truncated.
The body is protected over the attachments because the body is where the sender's actual question is. Raise **Draft
Input Tokens** if your model accepts more and you want less trimming.
## Getting started
1. **Start with two or three categories**, not fifteen. Broad, clearly-distinct buckets classify far more reliably than
a long list of overlapping ones, and you can split them later once you see the traffic.
2. **Let it create the folders.** Point the categories at folders that do not exist yet and let the first run create
them — that way the names always match exactly.
3. **Watch the first runs in the event timeline.** Every message shows the category chosen and the model's reason. That
reason is the fastest way to find a description that needs rewording.
4. **Fix misfiling in the descriptions.** They are the only lever there is, and they are the right one — nearly all
misfiling traces back to two categories whose descriptions overlap.
5. **Then put it on a schedule** (above), and the inbox drains itself.
6. **Turn on drafting last**, and only for the categories that need it. Read the first few drafts before you trust the
rest.
7. **Then ground the drafts.** Pick a knowledge agent, and tick **Knowledge Collections** on the categories that have
documents behind them. An ungrounded draft can only acknowledge a message; a grounded one can answer it.
## What it does *not* do
- **It never sends, and never deletes.** Moving relocates a message; drafting writes into your Drafts folder. Nothing
leaves the mailbox, and the platform has no way to send mail at all.
- **It does not read attachments to classify.** Classification uses the headers and the plain-text body only.
Attachments can feed a *drafted reply* (above), but never the choice of category.
- **It does not answer from documents the knowledge agent you picked cannot reach.** Narrowing a category only ever
restricts that agent's own scope; with no knowledge agent configured, every draft is written from the message and its
attachments alone.
- **It has no chat interface.**
- **It does not skip a message it cannot handle.** A run is all-or-nothing: if one message fails to classify, nothing in
that batch is filed and the whole batch is retried next run. That keeps a transient outage from scattering mail into
the fallback folder, but it does mean one persistently unprocessable message blocks the mailbox until you move it out
by hand. Watch for a run that reports an error every time with nothing filed.
::: warning Inbound mail is untrusted
Anyone can send your mailbox anything, and the body goes into the model's prompt. The agent is built so the worst case
is bounded: the model chooses from **your** list of categories and can only ever return a position in that list, so a
message containing instructions cannot invent a destination folder or make the agent do anything other than file mail.
The platform's PII guard anonymises personal data at the LLM gateway. Even so, treat the folder a message landed in as a
suggestion, not a verdict.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/5_agents/13_universal_agent.md
title: "Universal Agent"
description: "One assistant that decides for itself whether to search your knowledge, read attached files or recall memory, and keeps going until it can answer."
---
# Universal Agent
The **Universal Agent** works the way people expect a modern assistant to work. Instead of following a fixed sequence of
steps, it looks at each message and decides for itself what it needs: search your company's knowledge, read a file the
user attached, recall what it remembers about the user, or answer straight away. It keeps going, one tool after another,
until it can answer.
Every other agent on the platform follows a fixed workflow, so an admin picks the right one per use case and users have
to know which assistant to ask. A Universal Agent profile is configured once with the knowledge it may search, and users
ask it anything.
::: tip When to reach for this agent
Use the Universal Agent as a general assistant that combines your knowledge, the user's attachments and memory in one
conversation. When a use case needs a guaranteed, auditable sequence of steps — always retrieving from the same
collections, checking grounding before answering, escalating to an expert — use the dedicated blueprints such as the
[Document Intelligence Assistant](../5_document_intelligence_assistant/). They stay the predictable option.
:::
## What it does
```mermaid
flowchart LR
A[User message] --> B[Prepare the
conversation]
B --> C{Model decides}
C -->|needs a tool| D[Run the tool]
D --> C
C -->|can answer| E[Stream the answer]
```
1. **Prepare the conversation.** The agent's built-in instructions on using its tools, then your profile's own
instructions, are put at the front of the conversation, and the history is trimmed to the input budget.
2. **Decide and use tools.** The model chooses which tools to call, the platform runs them and hands the results back,
and the model decides again. It can call several independent tools in one round.
3. **Answer.** When the model has what it needs, its reply streams to the user with citations to the documents it used.
Nothing is loaded into the prompt in advance: a question that needs no lookup is answered directly, and one that needs
three lookups gets three.
### The tools
| Tool | What it does | Offered when |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| **Search our knowledge** | Searches the knowledge collections the profile allows, reranks the results and returns the best sections with ids to cite. The user's own `#` references are offered too. Only collections the asking user may read are ever searched. | The profile lists collections, or allows every collection the user can read. |
| **Read attached files** | Reads the files attached to the conversation. The model sees which files are attached and reads the ones it needs; a file too long to read whole returns the sections most relevant to what the model is looking for. | The user attached a document (images reach the model directly). |
| **Recall memory** | Searches what is remembered about the user and the organisation: preferences, facts and decisions from earlier conversations. | User or organisation memory is enabled on the profile. |
| **Code sandbox** | Runs commands and reads and writes files in the user's own code sandbox, in a folder per conversation that holds the conversation's attached files. A file the agent shows the user, such as a chart or a spreadsheet, is attached to the answer and stays downloadable. | The user switched on **Code Interpreter** in the chat. |
| **My files** | Lists and reads the user's own files in [My Files](../../10_chat_ui/14_my_files/): their uploads, chat attachments and files agents made for them. Read-only. | The user switched on **My Files** in the chat. |
More tools follow as the platform adds them: web search, web page fetch, image generation and the user's own file space
each add their tool to this agent.
Every tool call shows in the chat: the knowledge search and the files appear as sources, each call as a collapsible
block with what the tool returned, and the agent trace records each step.
### Staying within the model's context
Long conversations with many tool results can outgrow what the model can read. Before each decision, the agent checks
the size and, when needed, condenses the oldest material first: earlier tool results are summarised to what matters for
the question, then the conversation before the question becomes one summary. The chat shows a short status when this
happens. The current question and the latest results are never summarised; only when the latest results alone are too
large are they cut to fit, marked as cut. When even the question and the tools' descriptions do not fit, the agent
answers without its tools.
### Limits and approvals
A profile bounds how many decisions and tool calls one answer may take. At the limit, the agent answers with what it has
found and says that it stopped early. Any tool can be set to need the user's approval before it runs — every time, once
per answer, or once per conversation.
## What it does *not* do
- **It is not a fixed workflow.** The model decides which tools to use, so two similar questions may take different
paths. For a guaranteed sequence, use the dedicated blueprints.
- **It does not replace the Document Intelligence Assistant's retrieval.** Its knowledge search is simpler: each
database is searched with the embedding model it was indexed with and the results are reranked together, without the
Document Intelligence Assistant's per-retriever tuning or grounding check.
- **It never reads what the user may not.** Collections the asking user cannot read are not offered, even when the
profile lists them or the profile allows "every collection".
- **Its code sandbox is shared hardware.** Each user's sandbox home is separate, but all users share one container, so
it is not a hard boundary between users; see [Coding](../../10_chat_ui/6_coding/).
- **No tools from external systems yet.** MCP tool servers and handing off to other agents are planned follow-ups; use
the MCP Tool Agent for those today.
## Setting it up
1. **Grant the blueprint.** The Universal Agent is not part of a new tenant's default set; a sysadmin grants it (see
[Access control](../../16_multi_tenancy/4_access_control/)).
2. **Create a profile.** In **Admin > Agents > Blueprints**, select **Universal Agent** and click **Create Profile**.
Give it an Agent ID, a name, a description and an icon.
3. **Write the instructions.** Describe what this assistant is for and how it should answer. They are added after the
built-in instructions on using tools, so you do not need to explain the tools.
4. **Choose the knowledge.** Under **Knowledge Tool**, either list the databases and collections it may search, or turn
on **Every Collection the User Can Read**.
5. **Choose the model.** Pick a chat model that supports tool calls. The **Task LLM** condenses long conversations and
writes titles and follow-up questions, so a smaller model is fine there.
6. **Review the limits and approvals** under **Tools**, then save.
## Configuration reference
### Behaviour
| Field | Default | Description |
| ------------------------ | --------- | --------------------------------------------------------------------------------------------- |
| **Instructions** | *(empty)* | What this assistant is for and how it should answer, added after the built-in instructions. |
| **Maximum Input Tokens** | `128000` | The input budget. The conversation is trimmed and, inside the tool loop, condensed to fit it. |
### Knowledge Tool
| Field | Default | Description |
| -------------------------------------- | ------- | -------------------------------------------------------------------------------------- |
| **Every Collection the User Can Read** | Off | Offer every knowledge collection the asking user may read, instead of the listed ones. |
| **Collections** | — | Databases the model may search, each whole or narrowed to some of its collections. |
Under **Knowledge Search**, **Sections per Database** and the **Reranking Model** set how the search ranks what it
finds.
### Tools
| Field | Default | Description |
| ---------------------- | ------- | ----------------------------------------------------------------------------------------------------- |
| **Maximum Decisions** | `10` | How often the model may choose tools before it must answer. |
| **Maximum Tool Calls** | `20` | How many tool calls one answer may make in total. |
| **Disable Tools** | Off | Turn on to withhold some tools; the Disabled Tools picker appears only while it is on. |
| **Disabled Tools** | — | Tools this profile never offers, while Disable Tools is on. |
| **Approvals** | — | Tools whose calls the user must approve first: every call, once per answer, or once per conversation. |
Memory and attached-file settings are the same as on the other chat agents; see [Memory](../../15_memory/).
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/6_pipelines.md
title: "Data pipelines"
description: ""
---
# Data pipelines
Pipelines are automated workflows that transform documents into searchable knowledge bases for AI agents. They monitor
file storage locations, process documents when changes occur, and maintain vector databases that agents query for
information.
## Document processing workflow
Raw documents cannot be directly queried by agents. PDFs and Word files must be converted to text, split into manageable
chunks, and transformed into vector embeddings that enable semantic search. Pipelines handle this transformation
automatically.
```mermaid
flowchart LR
A[📄 Documents
SharePoint/Upload] --> B[📖 Parse
Extract text & structure]
B --> C[✂️ Chunk
Break into pieces]
C --> D[🔢 Embed
Convert to vectors]
D --> E[💾 Store
Vector database]
E --> F[🤖 Agents
Query & retrieve]
style A fill:#1e40af,stroke:#1e3a8a,stroke-width:2px,color:#fff
style B fill:#b45309,stroke:#92400e,stroke-width:2px,color:#fff
style C fill:#9f1239,stroke:#881337,stroke-width:2px,color:#fff
style D fill:#047857,stroke:#065f46,stroke-width:2px,color:#fff
style E fill:#6d28d9,stroke:#5b21b6,stroke-width:2px,color:#fff
style F fill:#b91c1c,stroke:#991b1b,stroke-width:2px,color:#fff
```
The diagram shows the complete flow from document ingestion through to agent queries. Each stage transforms the data to
make it searchable and retrievable.
## Automatic synchronization
Pipelines monitor data sources for changes. When a document is added, modified, or deleted, the pipeline processes the
change and updates the knowledge base. This keeps agent responses current without manual intervention.
## Orchestration with Dagster
Dagster orchestrates pipeline execution, handling scheduling, retries, and logging. Each processing step is tracked,
creating an audit trail from document ingestion through to storage. You can review pipeline runs to troubleshoot issues,
verify document processing, and monitor data quality.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/6_pipelines/1_fundamentals.md
title: "Fundamentals"
description: ""
---
# Pipeline fundamentals
## Implementation with Dagster
Pipelines are defined as Python code using Dagster. This approach enables:
- Custom processing logic for specific content types, business rules, or quality standards
- Conditional workflows where processing paths vary based on document content, source, or classification
- Error handling for network issues, data anomalies, or system failures
Pipeline code is reusable across different data sources and agents. Teams can build and modify pipelines without
changing agent code.
## Data sources
The platform includes a pre-built SharePoint connector for automated synchronization with SharePoint sites and document
libraries. Documents can also be uploaded manually through the UI for processing.
Custom connectors for additional sources require implementing I/O managers and operations specific to your data source
using the [pipeline SDK](../../../3_sdk/3_building_pipelines/).
## Quality and security controls
Pipelines can include validation and security steps:
- Content validation inspects incoming data for quality and completeness. Documents failing validation can be
quarantined for review.
- Security scanning checks for malicious content or policy violations before ingestion.
- Data sanitization applies transformation rules to redact sensitive information or enforce classification policies.
All pipeline actions are logged, creating an audit trail from document retrieval through processing to storage.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/6_pipelines/2_document_ingestion_pipeline.md
title: "Document ingestion pipeline"
description: ""
---
# Generic Document Ingestion Pipeline
The Generic Document Ingestion Pipeline is the platform's default pipeline for document ingestion. It transforms
documents from file storage into searchable knowledge bases that agents can query. All documents you want agents to
access must go through an ingestion pipeline.
The name is literal: this pipeline parses, chunks, embeds and indexes documents. It performs no retrieval and no
generation — that is the RAG agent's job, which queries what this pipeline produces.
## One pipeline, every knowledge database
A single deployment of this pipeline serves **every** knowledge database assigned to it, working out which database a
run belongs to as it goes. Creating a knowledge database therefore needs no new deployment, no configuration change, and
no restart — a database created in the admin UI is picked up automatically, and a document uploaded into it is normally
observed within about half a minute.
Each database keeps its own isolation: its own vector collection, its own document store, and its own file storage.
Sharing a pipeline shares the processing recipe, never the data.
A deployment can also run **its own** ingestion pipeline alongside this one — tuned differently, or built on entirely
different processing steps — and have it offered in the create-database dialog next to the platform's. See
[Building pipelines](../../../3_sdk/3_building_pipelines/) in the SDK documentation.
## Choosing models per database
Each knowledge database records the models it is ingested with, chosen when the database is created:
- an **embedding model**, which turns text chunks into the vectors agents search
- a **text-generation model**, used for the enrichment steps (summaries, table refinement, figure descriptions)
Databases that name no models use the deployment's defaults. Because a database's vectors are only comparable to other
vectors from the same embedding model, the choice is fixed for the database's lifetime — to change it, create a new
database and re-upload. The vector width follows automatically from the embedding model, so the two can never disagree.
## Processing stages
The pipeline processes documents through five stages:
1. Document parsing extracts text content and structure from PDFs, Word documents, PowerPoint presentations, and other
formats. The parser identifies headings, paragraphs, lists, and tables while preserving the document's organization.
2. Chunking splits large documents into smaller text chunks. The pipeline uses a structural parser that breaks text at
heading boundaries and paragraph breaks rather than at arbitrary character counts, ensuring each chunk contains
coherent information.
3. Embedding generation converts each text chunk into a vector embedding using an AI model. These embeddings capture
semantic meaning, enabling agents to find relevant information based on concepts rather than keyword matching.
4. Structural linking creates two types of connections between chunks:
- Sequential links connect each chunk to the chunks before and after it in document order. When an agent finds a
relevant chunk, it can retrieve surrounding chunks for complete context.
- Hierarchical links connect chunks to section summaries based on heading levels. If a chunk comes from subsection
3.2.4 (heading level 4), it links to a summary of section 3.2 (heading level 3), which links to a summary of
section 3 (heading level 2).
5. Summary generation creates hierarchical summaries for document sections. These summaries help agents understand
broader context when they retrieve specific details from nested sections.
## Storage and retrieval
After processing, the pipeline stores:
- Vector embeddings in the vector database for semantic search
- Original text chunks with metadata
- Sequential and hierarchical links between chunks
- Section summaries at each heading level
This creates a knowledge graph rather than disconnected text fragments. When an agent searches for information, it
retrieves relevant chunks and can navigate through sequential and hierarchical links to build complete context.
## Document lifecycle
The pipeline handles the complete document lifecycle:
When a document is added, the pipeline processes it through all five stages and stores the results in the knowledge
base.
When a document is modified, the pipeline removes all data from the old version before reprocessing the new version.
When a document is deleted, the pipeline removes all associated chunks, embeddings, links, and summaries from the
knowledge base.
This ensures agents never retrieve information from outdated or deleted documents.
## Document organization benefits
Structural linking provides the most value for documents with clear organization: technical manuals with sections and
subsections, legal documents with numbered articles, policy documents with hierarchical procedures, and long reports
where context spans multiple sections.
Documents without complex structure (announcements, emails, short articles) still benefit from semantic search and
sequential linking.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/7_processes.md
title: "Agentic Processes"
description: ""
---
# Agentic Processes
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/8_knowledges.md
title: "Knowledge management"
description: ""
---
# Knowledge management
AI agents need access to relevant information to answer questions accurately. The knowledge management system processes
your documents and makes them searchable through semantic retrieval.
## Structure
Knowledge organizes into three levels:
Knowledge databases are isolated containers at the top level. Each database has its own data, permissions, and
processing pipeline. Organizations typically create databases per department, project, or security classification.
Namespaces (called "collections" in the UI) group related documents within a database. They function like folders
organized by topic or purpose. A product database might contain "technical," "guides," and "troubleshooting"
collections.
Documents are the actual files - PDFs, Word documents, PowerPoint presentations. The system processes them automatically
after upload.
::: info Multilingual support
Database names, namespace labels, and folder descriptions support German, English, French, and Italian. The interface
displays labels according to user language preference.
:::
## Creating a knowledge database
You create knowledge databases yourself from the admin UI — no deployment, configuration change, or restart is involved,
and a new database starts accepting documents immediately.
When you create one you choose:
- **The ingestion pipeline** that will process its documents. Most deployments offer one, the Generic Document Ingestion
Pipeline; a deployment that ships its own pipeline offers that here too.
- **A text-generation model**, used for the enrichment steps — summaries, table refinement, figure descriptions.
- **An embedding model**, which turns text into the vectors agents search.
Leave the models unset to use the deployment's defaults. Because a database's vectors are only comparable to other
vectors produced by the same embedding model, the models are fixed once the database exists — to change them, create a
new database and re-upload.
Whoever creates a database is granted administrative access to it automatically, so you can use what you just made
without asking an administrator for a second step.
## Managing content
### Manual management
By default, databases allow manual control:
1. Create collections through the web interface
2. Upload documents to specific collections
3. Watch them process

You control what gets uploaded and where it lives. Uploading a document notifies the pipeline directly, so processing
normally begins within a minute or two rather than waiting for a scheduled run. A daily run still sweeps every database
as a safety net, catching anything a missed notification would otherwise have left behind.
### Syncing from an external source
Instead of uploading, a database can be given a **Source** when it is created, or later through **Edit source** on the
database. Pick the storage system (Google Drive or S3 — the two backends verified end to end), enter its credentials,
the folder to sync and optional include/exclude patterns. For Google Drive the service-account key is uploaded as the
`.json` file downloaded from Google Cloud, and the dialog shows the `client_email` to share the Drive folder with.
Credentials are stored encrypted and shown masked afterwards; re-saving the dialog without retyping them keeps the
stored ones, so a rotation is a matter of editing the source. The system then:
- Syncs files from the source on a daily schedule
- Creates collections automatically from the top-level folders of the synced root (files directly in the root are
skipped and counted in the run)
- Processes each synced file within a minute or two, like an upload
- Disables manual uploads, hand-made collections and manual document deletion for that database
If two top-level folders would end up as the same collection name (`hr docs` and `hr_docs`), the first one keeps the
name and the second is skipped: its files reach the data lake but are never ingested, and the ingestion run names the
skipped files and the reason in its metadata and logs. Other folders and the database's other files are unaffected;
renaming the folder at the source fixes it.
The external system becomes the source of truth. Your team continues working there, and the next sync brings changes,
including deletions, into the Swiss AI Hub. Giving a database that already holds uploaded documents a source asks for
confirmation first, because the first sync removes everything the source does not have. Clearing the source turns the
database back into a manually managed one; its files stay until you delete them.
### Deleting databases and collections
You can remove a whole knowledge database or an individual collection from the web interface. Both actions are
permanent.
- **Delete a collection** removes that collection's documents from storage and the vector index; the database and its
other collections stay.
- **Delete a database** removes the database entirely — every collection, all its documents, its vector collection, and
its file storage.
To prevent accidents, the confirmation dialog shows how many documents will be removed and requires you to type the
exact database or collection name before the delete button becomes active.
Deletion runs in the background. The moment you confirm, the item disappears from the list and stops accepting new
uploads, while the platform frees the underlying storage shortly afterwards. If you later re-upload a document that was
deleted, it is ingested again normally.
A database with a source can be deleted as a whole, which also stops its sync and forgets its credentials. Its
individual documents and collections cannot be deleted by hand, because the next sync would bring them back; remove them
at the source instead.
## Document processing
The system processes each uploaded document through several stages:
Parsing: MinerU extracts text, tables, figures, and structure from PDFs and Office documents. It handles complex
layouts, multi-column pages, and embedded content while preserving logical structure. A file is parsed once however
often the platform needs it: results are cached by file content for seven days, so re-uploading identical bytes, or an
agent reading a file the chat already parsed, does not run MinerU again.
Chunking: Large documents split into smaller pieces that maintain context. A 50-page manual becomes hundreds of chunks,
each preserving its relationship to surrounding content.
Metadata extraction: The system captures creation dates, authors, source information, and detected language. Agents can
filter results using this metadata.
Vector embedding: Text chunks convert to vector representations that capture semantic meaning. Agents find relevant
content based on concepts, not just keyword matching. A query about "vehicle speed limits" matches content about
"maximum velocity constraints."
## Inspection and debugging
The system provides visibility into document processing:
Document reconstruction shows how the parser interpreted your document. Check whether it correctly identified tables,
sidebars, and other structural elements.
Chunk inspection displays how the system segmented content, what metadata it extracted, and how it represents chunks for
retrieval. Useful when agents aren't finding expected content.
Processing status indicates whether documents are uploading, processing, or ready. The document list refreshes itself
every few seconds while a document on the page is still processing, so you do not need to reload to see it become ready.
A document counts as ready only once its embeddings have been written to the vector database — until then it is still
processing and agents cannot retrieve it, even though its text has already been parsed.
## Access control
Knowledge permissions follow the same two-level hierarchy as agent classes and agent instances: the database is the
parent, its collections (namespaces) are the children, and a rule on one level never implies its neighbours.
| Rule | Grants |
| ------------------------------------- | ------------------------------------------------------------------------------------------- |
| `aihub.admin.knowledge` | Create new knowledge databases and list the pipelines they can be assigned |
| `aihub.admin.knowledge.` | See the database, create collections inside it, delete the whole database |
| `aihub.admin.knowledge..` | Upload, edit, and delete documents in that collection; rename or delete the collection |
| `aihub.user.knowledge.` | See that the database exists, its name and configured pipeline, but none of its collections |
| `aihub.user.knowledge..` | Browse and search one collection |
| `aihub.user.knowledge..*` or `.>` | Browse every collection of one database |
| `aihub.user.knowledge.>` | Browse every database and every collection |
The seeded `AIHubKnowledgeUser` and `AIHubKnowledgeAdmin` roles bundle the last row and the first two rows respectively,
and the role editor offers the same three rules as presets. Admin rules imply the matching user rule. Per resource, the
role and tenant editors offer **Manage** on each database (`` together with `.>`) and **Use** and **Manage** on
each collection (`.`), so one collection can be handed to someone without the rest of its database.
`aihub.admin.knowledge.>` covers every existing database and collection but not the bare `aihub.admin.knowledge` root,
so a role that should create databases needs the root rule explicitly.
Creating a resource grants access to it automatically, mirroring agent instances: the creator receives a per-resource
admin role (`KnowledgeAdmin` or `KnowledgeAdmin`) and the tenant ceiling is raised to include the new rules
unless a broader rule already covers them. A database is granted as `aihub.admin.knowledge.` plus
`aihub.admin.knowledge..>`, so its admin role and its tenant also reach every collection in it — including the ones
a sync or ingestion pipeline creates, which are granted to no one when they appear. Deleting a database or collection
revokes those rules and roles again.
A new tenant's ceiling holds only `aihub.admin.knowledge`, never `aihub.admin.knowledge.>`. Databases are stored
deployment-wide, so that wildcard would show a tenant every other tenant's knowledge; a tenant sees only the databases
it created, plus any a sysadmin ticks for it in the tenant editor.
## Agent integration
An agent's knowledge scope is always explicit: it either names the collections to search or opts into every collection
of a database with the "Search all namespaces" switch. Leaving the selection empty is rejected when the configuration is
saved rather than silently widened to the whole database. Agents configured before this rule existed, with an empty
selection and no switch, fail validation until an administrator re-saves them with an explicit scope; the agent's error
message names the retriever field to fix.
Saving an agent configuration also checks that scope against the saving user's rules: each named collection needs
`aihub.user.knowledge..` (or a rule covering it), and opting into all collections needs a rule covering every
one of them (`....*`, `....>` or broader).
That check only covers whoever saved the agent. With **Restrict to the user's access** on, the knowledge agents (RAG,
expert RAG and namespace selection) also check every run against the person asking. They search only the configured
collections that person may browse under the rules above. A "Search all namespaces" retriever stays whole only for
someone who may browse the whole database; anyone else gets the collections they may read. The knowledge area and the
agents apply the same rules, so an agent never answers from a collection the person cannot open themselves. When nothing
of the agent's knowledge is readable, the agent says so rather than answering as if the documents held no answer. The
setting is on for new agents and off for agents saved before it existed, so their behaviour does not change unannounced.
Runs without a person, such as scheduled or mail-triggered ones, keep the agent's configured scope.
### Knowledge databases and My Files
Knowledge databases are shared, curated and indexed: documents are parsed, chunked and embedded so agents can search
them semantically. **My Files** is different. It is each user's own file space (their chat attachments, files agents
made for them and their uploads) and is not part of any knowledge database. Its files are not embedded or searched by
retrieval; an agent with the My Files option on reads them by name. Put documents that a team should find through search
into a knowledge database, and keep personal working files in My Files.
### Referencing collections in a chat
In OpenWebUI, any chat agent can be pointed at a collection with `#`, the way OpenWebUI references its own knowledge.
The `#` picker lists our collections as "Database / Collection", each only to users whose roles let them browse it. The
entries hold no content and OpenWebUI runs no retrieval on them: a referenced collection reaches the agent as a
reference, and the agent searches it with the database's own embedding model and adds the best matches to its context,
cited like any other document. In a knowledge agent's chat this search comes on top of the agent's configured retrieval.
The agent checks the reference against the asking user's rules again, and says when a referenced collection is not
available to them instead of answering from it. OpenWebUI administrators see every entry in the picker, since OpenWebUI
lets them bypass its access control, but they too get answers only from what they may read here.
Creating, renaming or deleting a collection shows up in the picker within seconds; collections a sync or pipeline
creates appear on the next reconcile, at most a minute later.
Agents connect to specific collections rather than entire databases. When configuring an agent, you specify which
collections it can search. A customer support agent might access "products" and "faq" but not "engineering."
Collection-scoped retrieval keeps agents focused on relevant content, improving both speed and accuracy.
Documents become available to agents after the pipeline processes them. The system tracks which source documents agents
used, enabling citation and verification of responses.
## Technical implementation
The architecture uses:
- FerretDB for document metadata and processing status
- Milvus for vector storage and semantic search
- MinerU for document parsing and structure extraction
- SeaweedFS for S3-compatible file storage
- LlamaIndex for chunking and embedding orchestration
Processing metadata lives in FerretDB, vector embeddings in Milvus, raw files in SeaweedFS. This separation optimizes
each component for its specific task.
## Limitations
No mixed modes: A database is either manually managed or synced from one source, not both. This prevents ambiguity about
who owns its content.
No manual chunk editing: The system generates chunks automatically from source documents. To fix incorrect chunks,
update the source document and reprocess.
No database merging: Databases remain isolated by design. Reorganization requires creating new structures and migrating
documents.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/8_knowledges/1_namespaces.md
title: "Organizing knowledge with collections"
description: ""
---
# Organizing knowledge with collections
Collections (technically called "namespaces") organize documents within a knowledge database. Each namespace is a
logical container for related documents.
## How collections work
When documents are ingested, they receive a collection label as metadata. This label travels with every chunk of the
document in the vector store. When an agent searches for information, it filters by collection to retrieve only relevant
documents.

Collections don't nest. They're flat metadata attributes. Agents can search across multiple collections simultaneously
without navigating a hierarchy.
## Access control
Collection access control operates at the agent level, not the user level. When you configure an agent to access
specific collections, every user interacting with that agent sees responses based on the same knowledge set.
All users get the same agent responses from a given agent instance. If users shouldn't access certain information, don't
give them access to that agent. The same agent workflow can be deployed multiple times with different collection
configurations, creating distinct instances for different audiences.
## Configuration
Agents specify which collections to search in their retrieval configuration. The platform searches all configured
collections in parallel and merges results by relevance scores. Update agent collection access through configuration
without code changes.
Restricting retrieval to relevant collections reduces search space and improves performance. Collections can follow
different retention policies and update cycles.
## Naming
Collection names can contain alphanumeric characters, hyphens, and underscores. Use clear, descriptive names like "hr,"
"sales-policies," or "technical_docs."
::: info Technical note
While the UI calls these "collections," they're technically implemented as "namespaces" in the codebase and appear as
namespace metadata on document chunks in the vector store.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/9_evaluations.md
title: "Agent evaluations"
description: ""
---
# Agent evaluations
Agent evaluations test and measure AI agent quality before and after deployment. You get data on whether your agents
deliver accurate, complete, and concise responses.
Evaluations test agents against predefined questions with known correct answers. Experiments are run in
[Langfuse](https://langfuse.com), the platform's built-in LLM observability tool. Swiss AI Hub auto-provisions most of
what Langfuse needs — your agents, an agent-calling connection, an evaluator LLM connection, and a prompt template — so
you can focus on three steps:
1. **Create a dataset** of questions and reference answers.
2. **Create evaluators** that score agent responses.
3. **Run experiments** against your agent and review the results.
One thing is not provisioned automatically and must be set once per deployment — see
[Before you start](#before-you-start-set-the-default-evaluation-model).
::: tip
This page describes the Swiss AI Hub workflow only. For Langfuse-specific details (dataset upload formats, evaluator
configuration, experiment comparison), see the [Langfuse documentation](https://langfuse.com/docs).
:::
## Before you start: set the default evaluation model
Langfuse keeps a **project-level default evaluation model** — the judge its managed evaluators use. Swiss AI Hub cannot
provision it: Langfuse exposes no API for this setting. Until a sysadmin sets it, every managed evaluator in the **Run
Experiment** wizard is greyed out and cannot be selected, with the tooltip *"Requires project-level evaluation model"*.
Set it once, after deployment:
1. Open Langfuse and go to **Evaluation → LLM-as-a-Judge → Default Evaluation Model**.
2. Click **Set up** and pick a chat model from the `AI-Hub LLM (Evaluators)` connection.
This is a one-time action per deployment — Langfuse runs a single project for the whole platform, so it does not need
repeating per tenant.
::: tip Upgrading a local environment created before September 2026
Langfuse issues both the agent call and the judge call **from its own container**, so both connections must carry
addresses reachable from there. `.env.dev` now ships them, but an `.env` copied before that change does not. Add:
```bash
LITE_LLM_PROXY_INTERNAL_BASE_URL='http://litellm:4000'
AIHUB_OPENAI_API_BASE_URL='http://host.docker.internal:8000/api/v1/active/openai'
```
Without them the API refuses to register the connections at startup, logging an error that names the missing setting —
the host-side defaults point at `localhost`, which inside the Langfuse container is Langfuse itself.
:::
## 1. Create a dataset
Datasets are collections of test questions with reference answers. Cover representative questions your agent will
receive, include clear reference answers and edge cases. Start with at least 10 question-answer pairs; 20-50 work
better.
You can create a dataset in two ways:
- **In the Swiss AI Hub Admin UI** — open `Datasets`, provide a name and description, add your question-answer pairs,
then save.
- **In Langfuse** — create the dataset directly, for example by uploading a CSV file with one row per question-answer
pair.
Either way the dataset is stored in Langfuse and immediately available for experiments.
 *Dataset overview in the Admin UI*
 *Adding test questions with reference answers*
::: warning Datasets created in the Admin UI are append-only
When editing in the Admin UI you can add new question-answer pairs at any time, but you cannot remove or edit items once
saved. To change a question, add a corrected item or edit the dataset in Langfuse.
:::
## 2. Create evaluators
Evaluators score each agent response, typically using LLM-as-a-judge. Configure them in Langfuse. Swiss AI Hub
provisions a dedicated evaluator LLM connection (`AI-Hub LLM (Evaluators)`), so judge models are available out of the
box. Document-extraction models (OCR and vision-language) are deliberately left out of that list — LiteLLM reports them
as chat models, but they cannot grade text.
Recommended dimensions to score:
| Metric | What it measures |
| ------------ | ------------------------------------------------------------------------------------------------------------- |
| Correctness | Factual accuracy compared to the reference answer. Free of misinformation, hallucinations, or contradictions. |
| Completeness | Addresses all parts of the query, including multi-part questions and implicit needs. |
| Conciseness | Efficient and direct. Avoids irrelevant tangents, redundancy, or excessive filler. |
## 3. Run experiments
Open a dataset card in the Admin UI and click **Run Experiments** to jump to the dataset in Langfuse, then create an
experiment there:
- **Select the agent to test.** Online agents appear automatically as selectable models, named
`/` — Swiss AI Hub continuously syncs running agents to Langfuse, so no manual registration is
needed.
- **Use the `ai-hub-agent` prompt template.** This auto-provisioned template maps each dataset question to a request
against the agent's OpenAI-compatible endpoint, so Langfuse calls your real agent through the platform.
- **Attach the evaluators** from step 2 and run the experiment.
Run experiments before deploying a new agent, after significant changes to its configuration or knowledge base, and
regularly for quality monitoring. Every run is stored in Langfuse, so you can compare runs side-by-side to confirm a
change improved quality.
When interpreting results: low correctness usually points to knowledge base gaps or retrieval issues, low completeness
to missed parts of multi-part questions, and low conciseness to overly verbose responses. Adjust the agent's knowledge
base, system prompts, or retrieval settings accordingly, then re-run to verify improvements.
## What's not implemented
The following features are not currently implemented:
- Bias monitoring and model drift detection: No automated bias detection, fairness metrics, or drift detection. Langfuse
experiments and OpenTelemetry tracing provide foundational capabilities that could be extended.
- Production A/B testing: No integrated traffic splitting or parallel testing of agent variants in production.
Pre-deployment comparison via Langfuse experiments is supported.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui.md
title: "Chat Interface"
description: ""
---
# Chat interface
The Swiss AI Hub uses **Open WebUI** as its primary chat interface. Open WebUI is an open-source project with a
comprehensive feature set that includes document handling, user management, tools, voice interaction, and collaborative
features. Rather than building a custom chat interface, the platform integrates this existing solution and extends it
with custom capabilities.
## Why use an existing solution?
Building a production-grade chat interface requires significant development effort. Open WebUI already provides this
functionality with an active maintenance community. The Swiss AI Hub team can focus on capabilities specific to
enterprise AI deployment instead of recreating standard chat features.
As Open WebUI evolves, the Swiss AI Hub receives these improvements through regular updates. The platform benefits from
community-driven development, documentation, and deployment patterns without dedicating resources to maintaining basic
chat infrastructure.
## Custom extensions
The Swiss AI Hub extends Open WebUI's functionality in three main ways.
The interface embeds directly into the Swiss AI Hub suite rather than running as a separate application. Users access
chat capabilities through unified navigation without switching contexts or re-authenticating.
When AI responses draw from organizational knowledge bases, users can view the specific documents and passages that
informed the response. This source attribution enables verification of AI-generated content.
Custom tracing shows step-by-step agent execution, including reasoning chains, tool invocations, and decision points.
This visibility supports debugging and regulatory compliance requirements.
## Deployment considerations
Organizations using the Swiss AI Hub avoid the cost of building and maintaining a custom chat interface. Development
resources go toward enterprise-specific requirements rather than commodity functionality.
The open-source foundation provides flexibility. Organizations can fork or modify the chat interface if requirements
change. Platform viability doesn't depend on a single vendor's roadmap.
The existing production deployments and active community reduce technical risk compared to custom development.
Organizations deploy proven technology with established patterns.
## Documentation structure
This section covers the chat interface integration from several angles:
- [Integration architecture](11_integration_architecture/) explains how Open WebUI connects to the suite and handles
communication
- [Source attribution](3_chat_with_your_data/) describes the custom extensions for knowledge retrieval visibility
- [Observability](10_observability/) covers execution tracing and workflow transparency
- My Files is each user's own file space: attachments, files agents made, and uploads
- [Feature overview](1_feature_overview/) catalogs the capabilities inherited from Open WebUI
- [Strategic rationale](12_strategic_rationale/) analyzes the decision to integrate an existing solution
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/1_feature_overview.md
title: "Feature overview"
description: ""
---
# Feature overview
Through Open WebUI integration, the Swiss AI Hub provides a comprehensive feature set. This section documents key
capabilities available through the chat interface.
## Core chat capabilities
Messages stream in real-time as AI generates responses. Users can begin reading before generation completes rather than
waiting for complete responses.
The interface maintains conversation context across multiple turns. Users can ask follow-up questions or request
clarifications without re-establishing context.
Users can edit previous messages to refine queries, delete messages to remove irrelevant content, or regenerate
responses to explore alternative outputs.
The interface provides conversation categorization through tags, searchable history, and archiving. Users can maintain
organized libraries of interactions, finding and resuming previous conversations. Conversations can be deleted, shared
with others, or cloned to split off in different directions.
Full markdown rendering enables rich text formatting in both user messages and AI responses - headers, lists, tables,
code blocks with syntax highlighting. LaTeX support enables mathematical notation for technical and scientific
applications.
## Multi-modal interaction
Users can dictate messages rather than typing. Supported languages include English, German, and Swiss-German.
AI responses can be rendered as speech, supporting accessibility requirements and enabling audio-based consumption. This
benefits visually impaired users and scenarios where audio consumption is preferable.
Users can upload documents directly into conversations, asking questions about document content or requesting analysis.
The interface handles PDFs, Office documents, and text files.
For AI models supporting vision capabilities, users can include images in conversations, requesting analysis,
description, or processing.
## Model management
Users can interact with multiple AI models within the same interface, selecting models based on capability requirements,
cost considerations, or performance characteristics.
Advanced users can adjust model parameters - temperature for creativity control, token limits for response length,
presence penalties for repetition reduction.
Organizations can define their own "models" with custom system prompts added to base models, similar to GPTs in OpenAI.
All parameters can be preset, knowledge through RAG can be provided, and custom tools made for specific models. Users
select presets rather than configuring manually.
## Retrieval-augmented generation
Organizations can provide AI models with access to custom document collections called knowledge in Open WebUI. The
interface handles document upload, processing, and retrieval configuration.
When RAG capabilities are enabled, AI responses incorporate information from configured knowledge bases, providing
answers grounded in organizational knowledge rather than generic training data.
The native interface provides indicators when responses incorporate retrieved information. The Swiss AI Hub's enhanced
source attribution extends these capabilities as documented in the source attribution section.
Administrators can manage document collections and configure retrieval parameters through integrated management
interfaces.
## Collaboration and sharing
Users can share conversations with colleagues, enabling collaborative AI interaction. Shared conversations maintain full
context, allowing recipients to review history and continue conversations.
Users can annotate AI responses with feedback - marking responses as helpful or problematic, providing correction
guidance, or adding contextual notes.
For deployments enabling community interaction, users can participate in leaderboards recognizing productive usage,
share effective prompts or interaction patterns, and learn from colleagues' successful applications.
Multiple users can work within shared workspace environments, accessing common conversation histories, shared knowledge
bases, and collaborative interactions that support team-based work patterns.
## Administration and security
Administrators define user roles with granular permissions controlling access to specific models, features, or
administrative functions.
Administrative interfaces provide user provisioning, authentication configuration, and access revocation capabilities.
Integration with enterprise authentication systems - OAuth, LDAP - enables centralized user management.
For programmatic access, administrators can generate and manage API keys enabling external systems to interact with chat
capabilities. API key permissions can be scoped to specific models or operations.
Comprehensive logging captures user activities, model interactions, and administrative operations, creating audit trails
supporting compliance requirements and security monitoring.
### Administrator access to user content
Administrative rights do not include reading other people's content. By default a chat administrator sees only the files
they uploaded themselves, and no one - administrators included - can export other users' conversations. This holds
across tenants: administration is platform-wide, but visibility into uploads and chats is not.
Deployments that need administrative oversight, for support or compliance workflows, can enable it by setting
`OPENWEBUI_BYPASS_ADMIN_ACCESS_CONTROL` (files, plus workspace models, knowledge, prompts and tools) and
`OPENWEBUI_ENABLE_ADMIN_EXPORT` (chat export) to `true`. Both default to `false`.
Note that the chat administrator role is separate from the platform's tenant roles. It is granted by the `AIHubSysAdmin`
realm role only; a tenant administrator signs in as an ordinary user here and has never been able to see other users'
files or chats. See
[ADR: Scope OpenWebUI Admins to Their Own Files and Chats](/arc42/decisions/2026_09_07_openwebui_admin_scoped_to_own_data.md).
## User experience
The interface adapts to different screen sizes and devices - desktop, tablet, mobile - maintaining functionality and
usability across form factors. Users can initiate conversations on desktop and continue them on mobile seamlessly.
The interface can install as a progressive web app, providing native application-like experiences including offline
capability, push notifications, and home screen presence without requiring app store distribution.
Users can select from multiple visual themes - light mode, dark mode, high-contrast options - matching interface
appearance to personal preferences and environmental lighting.
## Advanced capabilities
For supported models and configurations, the interface can execute code snippets, enabling interactive programming
assistance, computational problem solving, and algorithm prototyping within conversational contexts.
The same sandbox lets models produce files — reports, spreadsheets, slide decks, charts, audio and video — which the
model then opens in the user's file viewer. This needs no code from the user; see
[File generation](../13_file_generation/) for the supported formats and the recommended model.
Agents can use the sandbox too. With **Code Interpreter** switched on, the Universal Agent runs commands and writes
files in the user's own sandbox home, and the files it makes are attached to its answer. Every user has My Files, one
place for the files they attached, the files agents made for them and their own uploads.
Mermaid diagram support enables AI-generated visualizations - flowcharts, sequence diagrams, state machines - rendered
directly within conversations. This supports systems design, process documentation, and visual explanation.
The extensibility framework enables integration of custom processing pipelines, tools, and functions. Organizations can
extend chat functionality with business-specific operations without modifying core interface code.
When configured, AI models can access web search capabilities to incorporate current information beyond training data.
This supports queries requiring up-to-date information or verification against current sources.
## What this provides
Organizations deploying the Swiss AI Hub gain these Open WebUI capabilities immediately, without development investment.
As the Open WebUI community adds new features, the Swiss AI Hub benefits through standard update cycles.
Organizations gain mature functionality developed by global communities, extended with enterprise-specific capabilities
like source attribution and execution tracing.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/2_chat_messages.md
title: "Chat Messages"
description: ""
---
## Chat Messages
Using Actions, advanced operations can be performed on model responses. Actions are the small icons underneath each
response from the model. These can perform different things, from reading the response aloud to giving feedback on the
quality of the answer.
The response can be edited.

Copy the response to clipboard.

Read the response aloud.

Generate an image based on the response.

Positive feedback can be given.

Here the response can be rated and enriched with details in order to better evaluate how well the models perform.

Negative feedback can also be given.

The response can be continued.

The response can be regenerated.

Regenerated responses can be deleted again.

---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/3_chat_with_your_data.md
title: "Chat with your Data"
description: ""
---
To upload files click the "More" button.

Then select "Upload Files". In the pop up navigate to the files you want to use and upload them.

After uploading the files write ask a question.

The model will use the files to answer the question.

::: tip Several files in one conversation
An AI agent reads the files of the whole conversation on every message, so you can keep asking about a document you
attached earlier, and an edited or regenerated message sees only the files of its own branch. Large documents are read
whole when they fit; otherwise the agent keeps the parts that matter for your question and tells you that its answer is
based on part of the file.
:::
::: tip Point an agent at company knowledge with #
Type `#` in the message box to pick one of the company's knowledge collections. The agent searches it for your question
and cites what it finds, but only if your roles let you read the collection.
:::
::: tip Too large for the model
If your message itself, for example a long pasted text, is larger than the model can take at all, the agent says so in
the chat instead of failing with an error. Shorten the message or split it up.
:::
Click on a reference to view which parts where used to answer the question.

The citation gives a clear rundown of the different parts used to generate the answer.

---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/4_chat_with_company_knowledge.md
title: "Chat with your company knowledge"
description: ""
---
# Chat with your company knowledge
The Swiss AI Hub shows users which knowledge documents and passages informed AI responses. This transparency helps users
verify accuracy and understand the basis for AI conclusions.
## Why this matters
When AI systems use Retrieval-Augmented Generation (RAG) with organizational knowledge bases, users need to verify where
information comes from. Standard chat interfaces, including native Open WebUI, don't systematically show which documents
or passages informed responses.
Regulated industries and quality-critical domains require evidence chains for AI-generated content. Decision-makers need
to verify that responses align with authoritative sources and current policies. Without source attribution, verification
requires manual research.
Organizations maintaining knowledge bases need feedback on retrieval quality. Source attribution shows whether agents
access appropriate documents and reveals knowledge gaps for common queries.
## How it works
A custom source display panel activates when AI responses reference organizational knowledge. The interface provides a
control for viewing sources without leaving the conversation.
Activating source display opens an adjacent panel. Users see their conversation on one side and source details on the
other, enabling simultaneous review of responses and supporting materials.
Sources organize by document. For each document, users see metadata - database location, namespace, title - followed by
specific passages that contributed to the response.
The system shows specific passages (nodes) retrieved and provided to the AI. Users see exact text chunks, their context
within source documents, and relevance scores.
## Interactive exploration
Source entries link directly to full documents within the knowledge management service. Users can click through from a
retrieved passage to view the complete document.
Each passage includes surrounding context like heading hierarchies and document structure. This helps users understand
where the passage sits within its source.
The display differentiates between sources used in response generation and sources retrieved but not selected. A toggle
controls this distinction. This helps users understand the AI's source selection.
Repeated interactions reveal which documents address specific topics, where knowledge gaps exist, and how information
organizes.
## Technical implementation
During agent execution, the platform captures retrieval events documenting which documents and nodes were accessed.
These events include identifiers linking content to specific documents in knowledge management systems. When users
request source display, the platform queries these events to construct the view.
Source display coordinates data from thread management (conversation context), event tracking (retrieval records), and
knowledge management (document details). The architecture maintains service boundaries while enabling this coordination.
Source data loads on-demand when users request visibility rather than preloading for every message. Users get immediate
access when requested. The system doesn't load source data for conversations where users don't need verification.
When retrieval accesses numerous sources, the display prioritizes the most relevant content. Sources directly
contributing to responses appear first. Lower-relevance or unused sources are available through expanded views or
filters.
## What this provides
When users can verify AI responses against authoritative sources, trust increases. Verification addresses the "black
box" concern.
For regulated industries, source attribution provides evidence chains for AI-assisted decisions. Compliance audits can
trace recommendations to approved materials.
Knowledge managers validate retrieval quality through source attribution. Consistently irrelevant sources or knowledge
gaps guide improvement - adding documents, refining organization, or adjusting retrieval parameters.
Users learn how knowledge bases organize and what information exists. This reduces dependency on AI as users develop
direct knowledge of organizational resources.
For high-stakes decisions - regulatory compliance, financial commitments, operational changes - users can verify AI
suggestions before acting.
## Knowledge management feedback
When users examine sources and find irrelevant content, this feeds back into retrieval system tuning. Patterns of source
examination followed by revised queries indicate quality issues.
When conversations reveal no appropriate sources for common queries, this signals knowledge base gaps. Organizations
prioritize document acquisition based on these signals.
Source attribution generates usage data showing which documents AI systems reference most frequently. This informs
curation priorities. Frequently accessed documents need careful maintenance. Unused documents may indicate organization
issues.
When source attribution shows responses drawing on outdated versions, organizations receive signals to update knowledge
bases.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/5_websearch.md
title: "Web search capability"
description: ""
---
# Web search capability
Chats with a plain language model can reach out to the web to answer questions requiring current data beyond the model's
training. Organizations control this through configuration.
::: warning
Web search applies to plain model chats only. AI-Hub agents ground their answers in the knowledge bases they were
configured with, so the "Web Search" button is not offered when an agent is selected in the model picker.
:::
Select "Web Search".

Enter something you want to search for.

The model will take some time coming up with a search query and then executing that search. After which it will output
what it found.

The websites it retrieved the information from are listed as references.

## Search configuration
Web search can be enabled or disabled platform-wide, and restricted to specific user roles through role-based access
control, so different policies apply to different use cases or user groups. It can be modified at runtime without system
changes.
## Search restrictions
Organizations can limit searches to approved domains, trusted sources, or specific content types like academic
institutions or government websites.
Search queries can be validated, filtered, or transformed to ensure compliance with data protection policies and prevent
sensitive information leakage.
Retrieved web content can be validated against organizational standards before presentation.
Search restrictions can align with industry regulations, internal policies, or contractual obligations.
## Source attribution
When a chat uses web search, the system provides traceable attribution of external sources.
Users see clear distinctions between internal knowledge and external web sources. Web results appear as structured,
clickable citations with URLs, titles, and content previews. Users receive information about why specific sources were
retrieved.
Every search query, retrieved result, and user interaction is captured for audit purposes. The entire search process -
from query validation through result filtering to presentation - is traceable through the observability infrastructure.
## Use cases
Users supplement the model's own knowledge with current market data, regulatory updates, industry news, or technical
documentation from external sources.
Where a topic is too recent or too niche for the model to know, external information fills the gap while sources stay
clearly attributed.
Claims can be validated against authoritative external sources.
## Governance and security
Organizations can ensure that search queries don't expose sensitive information to external providers through query
validation and filtering.
Web content is validated against organizational standards before presentation, preventing inappropriate or unreliable
sources from reaching users.
The permission system allows control over which users or groups can use web search for which purposes.
Complete audit trails and transparent source attribution support compliance with data governance regulations and
industry standards.
Configurable restrictions and validation mechanisms let organizations balance the value of external information with
their risk tolerance and compliance obligations.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/6_coding.md
title: "Coding / Software Development"
description: ""
---
# Coding / Software Development
OpenWebUI integrates with **Open Terminal** — a sandboxed Linux environment with Python 3.12, common document libraries
(pandas, openpyxl, xlsxwriter, python-docx, python-pptx, reportlab, fpdf2, weasyprint, pypdf, matplotlib, Pillow, numpy,
scipy, lxml, PyYAML) and the `ffmpeg` and `pandoc` command-line tools. Code runs in an isolated per-user environment, in
that user's own home directory inside the sandbox.
::: warning Requirements & current limitations
- **Plain LLM models only, with Native Function Calling enabled.** OpenWebUI exposes the sandbox to the model as a set
of tools (`run_command`, `write_file`, `display_file` and so on) resolved from its terminal-server integration — not
through the built-in `execute_code` tool. **The model must use Native Function Calling**: only then are those tools
handed to the model as real function definitions, which is what lets it run a step, read the result and continue.
Under Legacy, OpenWebUI falls back to a single prompt-based tool-selection pass, which is not enough for a multi-step
build. **Kimi-K2.6 is set to Native by default**; every other model defaults to Legacy. To use another model, switch
Function Calling to Native for that conversation in the chat's Controls → Advanced Params. A change in Admin →
Settings → Models does not last: AI-Hub resets it the next time the API starts, and an operator changes a model's
default in the LiteLLM configuration instead. Models without function-calling support cannot drive the sandbox at all.
- **The terminal has to be active for the conversation.** The tools are resolved only when a terminal is selected in the
chat.
- **Among AI-Hub agents, only the [Universal Agent](../../5_agents/13_universal_agent/) runs code.** With Code
Interpreter switched on it works in the same sandbox home as your plain-model chats, in a folder per conversation, and
attaches the files it shows you to its answer. Other agents do not run code.
:::
There are two main ways to use code execution.
1. Ask the model to write and run code. Mentioning a specific goal (e.g. "create a chart", "process this data") gives
the model the context to generate and execute Python code that produces the result directly in the chat.
2. Provide existing code and ask the model to run or improve it.
## Coding with the LLM
Enter a prompt mentioning the "Pydiode environment" in order to generate code.

Using the "Run" button the code can be tested directly inside the chat.

::: tip Two execution paths — only one of them can produce a file
**Model-driven execution**, where the model calls the sandbox's tools itself, runs server-side in **Open Terminal** with
the libraries above, inside the user's own home directory. This is the only path that can produce a downloadable file.
The manual **"Run" button** on a code block — and OpenWebUI's built-in `execute_code` tool, whose
`CODE_INTERPRETER_ENGINE` defaults to `pyodide` — both use the **Pyodide** engine instead: in-browser WebAssembly, which
is lightweight, limited to its bundled packages, and **cannot write files to the server**. Charts come back as inline
images on that path, not as files.
:::
After running the code snippet prints the result below the cell.

## Executing existing code
Select "Code Interpreter".

Encase the code in back-ticks to mark it as code for execution.

When the code has run through the result is printed out.

## Generated files
Files written during code execution (reports, spreadsheets, charts, etc.) are created in the user's own home directory
inside the sandbox (`/home/`). The model surfaces a finished file by calling `display_file`, which opens it in the
user's file viewer; the files also stay reachable through the terminal panel's own file browser.
Producing documents is a capability in its own right and does not require writing any code. See
[File generation](../13_file_generation/) for the recommended model, the verified list of output formats, and the
limitations that apply to them.
## Isolation and limitations
::: warning Shared-container, per-user isolation
Open Terminal runs in **a single shared container** with `OPEN_TERMINAL_MULTI_USER` enabled: each user gets a separate
Linux account and home directory (`/home/`), and standard filesystem permissions keep one user's files private
from another. This is **per-user isolation inside one container**, not a container-per-user model — all users share the
same kernel, CPU, memory, `/tmp`, and process list. It suits small, trusted groups; it is **not** a hard multi-tenant
security boundary. Treat the sandbox as a convenience for collaborators, not as a barrier between mutually untrusted
parties.
:::
::: tip Stored files grow over time
Each user's generated files persist on the host (the `open-terminal` `/home` volume) and are **not** cleaned up
automatically — there is currently no retention or quota policy. Disk usage grows with use; operators should monitor the
volume and prune old per-user data manually until an automated cleanup/TTL is added.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/7_screen_sharing.md
title: "Screen Sharing"
description: ""
---
# Screen Sharing
Select "More". 
Then select "Capture".

Choose what should be captured, a tab from the browser, a window, or the entire screen.

Once selected it can be shared.

The captured image appears in the chat box.

Type "What can you tell me about this screenshot?"
Ask something about the image.

And the model will analyze it and give an answer.

Models that don't support vision capabilities will produce an error.

---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/8_voice_input.md
title: "Voice Input"
description: ""
---
Begin a voice recording.

A timeline indicates how long the recording has lasted.

To complete the recording and transcribe the audio as a prompt select the check mark.

The recorded audio will be displayed as input which can be altered or directly sent to the model.

---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/9_feedback.md
title: "User feedback"
description: ""
---
# User feedback
Users can rate AI responses through thumbs up/down controls during conversations. This feedback helps evaluate model
performance and can inform future improvements.
## How feedback works
Users leave a thumbs up for responses they like or a thumbs down for responses they don't. The system captures a
snapshot of the chat when users provide ratings.
Feedback can happen in two modes. Arena mode randomly selects models for unbiased comparison. When rating in arena mode,
giving a thumbs up to one response automatically assigns a thumbs down to the alternative response.
Normal interaction mode lets users rate responses during standard chat without special activation. For leaderboard
impact in this mode, responses need to come from different models for comparison.
## Leaderboard and rankings
Ratings feed into a personalized leaderboard using an Elo rating system similar to chess rankings. This shows which
models perform best based on actual usage.
The platform supports topic-based reranking through tagging. Users can compare model performance across specific domains
like customer service or technical support. Topic tagging happens automatically with manual override options for
accurate categorization.
## Data handling
All evaluation data stays on the user's instance by default. Organizations control whether to opt in for community
sharing. Captured feedback can support future model fine-tuning efforts.
The feedback system helps organizations identify which models work best for their specific use cases and where
improvements might be needed.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/10_observability.md
title: "Observability"
description: ""
---
# Observability
The Swiss AI Hub provides visibility into agent execution processes. Users can see the steps an agent executed, the
decisions it made, and the data it processed.
## Why observability matters
Traditional AI systems show results without showing reasoning. Users see responses but can't see intermediate steps,
decision logic, or where things went wrong.
Decision-makers hesitate to rely on systems they can't understand or validate. When AI produces unexpected results,
users need information to determine whether the system malfunctioned or correctly processed flawed input.
Development teams debugging unexpected agent behavior need more than log files. Quality assurance teams need systematic
ways to validate agent behavior.
Regulated industries need to explain and justify AI-assisted decisions. Compliance frameworks require evidence showing
how systems reached conclusions and where human oversight occurred.
## How execution tracing works
The platform captures detailed records of agent workflow execution.
Execution traces show the complete sequence of steps the agent executed - decision points, tool invocations, knowledge
retrievals, intermediate calculations. Users see structured processes rather than mysterious computations.
Each workflow step consumes input events and produces output events. The trace display shows how data transforms through
the workflow. Input messages become classification events, then retrieval requests, then synthesized responses.
Traces include timing information for each step. Users can identify performance bottlenecks and assess whether slow
responses come from complex reasoning or infrastructure delays.
When workflows include conditional logic, traces show which branches executed and why. This helps users validate that
agents apply appropriate logic to specific scenarios.
## Interactive exploration
Trace display opens an adjacent panel within the chat interface. Users correlate chat responses with workflow steps
without switching applications.
Traces present information hierarchically - workflow overview, step execution, event data. Users drill into areas of
interest without excessive detail for straightforward operations.
At the most granular level, users examine complete event data - the JSON structures flowing between workflow steps. This
supports sophisticated debugging and validation.
From trace views, users navigate to related capabilities - knowledge documents accessed during retrieval, agent
configurations, system logs.
## Langfuse tracing integration
The platform uses Langfuse, an open-source AI observability platform.
The implementation follows OpenInference semantic conventions. Trace data uses standardized formats compatible with
industry observability tools.
The system captures semantic events - LLM invocations, retrieval operations, embedding generations - as structured trace
spans. These capture AI-specific concepts like token usage, retrieval relevance scores, and model selection.
When workflows involve multiple agents, traces maintain correlation across interactions. Users can follow execution
flows spanning multiple agents.
Execution traces persist beyond conversation sessions. Users can review historical executions for quality assurance,
compliance documentation, or incident investigation.
## What this provides
When agents behave unexpectedly, execution traces enable rapid troubleshooting. Support teams examine execution
sequences, identify failure points, and resolve issues without extensive reproduction.
Quality assurance teams validate agent behavior systematically. By examining how agents process inputs and handle edge
cases, QA verifies correctness before production deployment.
Developers identify optimization opportunities from trace data. Traces revealing inefficient patterns or unnecessary
steps guide refinement efforts.
For regulatory compliance, execution traces document how systems reached conclusions. Audits can review traces
demonstrating appropriate data usage and human oversight at required points.
When users can examine execution details, confidence increases. The ability to inspect processes makes AI more
understandable.
## Developer use
Developers building agents use trace visualization during development. Rather than log files or print statements,
developers watch workflows execute through trace interfaces.
Automated tests capture execution traces. Tests can verify that agents invoked appropriate tools, accessed correct
knowledge sources, and followed expected paths.
Trace timing data enables performance profiling. Developers identify slow steps, quantify configuration impacts, and
validate optimization results.
Execution traces serve as living documentation. Developers reference actual traces showing how agents behave in
practice.
## Privacy and security
Access to the Langfuse UI (`langfuse.`) is restricted to system administrators: only users with the
`AIHubSysAdmin` Keycloak realm role can log in. The restriction is enforced at the Keycloak login itself, so users
without the role are denied before any trace data is reachable.
Trace visibility respects the permission system. Users can only view traces for conversations they participated in or
are authorized to audit. Administrative access requires explicit permissions.
The platform can redact sensitive information from traces - personally identifiable information, confidential data -
while preserving workflow structure.
The system logs trace access, documenting who reviewed which traces and when. This supports compliance requirements and
detects inappropriate access.
Organizations configure trace retention policies balancing observability value against storage costs and regulations.
Traces can expire after periods, or selective retention can preserve significant conversations while aging out routine
interactions.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/11_integration_architecture.md
title: "Integration architecture"
description: ""
---
# Integration architecture
The Swiss AI Hub embeds **Open WebUI** directly in its interface rather than linking to a separate deployment. This
maintains a unified user experience while keeping the open-source component separate from the platform infrastructure.
## How embedding works
The platform uses an iframe to render the complete Open WebUI interface within the suite's service area. Users see a
single integrated application. The architecture maintains separation between the open-source component and platform
infrastructure.
When users navigate to the chat service, Open WebUI occupies the full service area. The suite navigation sidebar stays
accessible so users can switch to other services without losing their chat context.
The embedding preserves Open WebUI's complete interface and feature set. Keyboard shortcuts, drag-and-drop file
handling, and conversation management work as they do in the standalone application. The embedded interface adapts to
different screen sizes - desktop displays provide expanded workspace while mobile devices maintain functional access.
## Communication between components
The iframe and suite platform communicate using the browser-standard PostMessage API. This provides secure cross-origin
messaging while maintaining security boundaries between components.
The chat interface and platform exchange structured messages for user interactions, navigation requests, and state
synchronization. When users request platform capabilities within the chat interface - like viewing knowledge sources or
execution traces - the chat posts messages that trigger navigation and data display.
Messages follow defined contracts that specify intent, parameters, and behaviors. Types include source display requests,
tracing visibility requests, and context synchronization.
If message passing fails or the platform can't fulfill requests, users receive feedback rather than silent failures.
## Authentication and security
The platform and Open WebUI share authentication through OAuth. Users authenticate once to the Swiss AI Hub suite, and
this propagates to the embedded instance.
The platform enforces permission boundaries for AI models, knowledge bases, and agent capabilities. Users can't access
through the chat interface what they can't access through other services.
Sessions stay synchronized between platform and chat. Logging out from the suite terminates the chat session. Timeouts
and renewals coordinate across both components.
Communication uses secure channels with encryption and validation. The iframe integration includes security headers and
content security policies to prevent cross-site scripting.
## Model visibility and access control
The platform manages which agents each user can see in Open WebUI. Since Open WebUI's pipe discovery runs without user
context, AI-Hub pushes permission state into Open WebUI rather than filtering at the pipe level.
**How it works:**
1. **Groups**: AI-Hub creates Open WebUI groups for each tenant-role combination (named `aihub:{tenant}:{role}`), with
memberships synced based on email matching between both systems. A user who holds a role in their active tenant but
has not opened the chat yet gets their Open WebUI account created over SCIM in the same sync, so their models are
there on their first chat visit; the chat login then links to that account
2. **Workspace models**: For each online agent, AI-Hub creates a workspace model that delegates to the corresponding
pipe function
3. **Access grants**: For each workspace model, AI-Hub computes which groups have access using the platform's permission
system (with tenant ceiling enforcement), then sets `access_control` on the model
The provisioner runs at API startup, when an agent instance is created, renamed, or deleted (reflected immediately in
the model picker), when the set of online agents changes (the periodic 60-second reconciler), when users switch tenants,
and when roles, tenants, or user-role assignments are modified. All changes propagate immediately.
**Reliability in multi-replica deployments:**
- **Debouncing**: Rapid access entity mutations (e.g. bulk user assignments) are collapsed into a single sync call using
a 2-second quiet window
- **Distributed locking**: Each sync operation acquires a Redis lock, so concurrent API replicas never race. Model syncs
skip while another holds the lock; access syncs wait for it, so a role change made during a running sync is still
applied
- **Change detection**: The discovery service stores a SHA-256 hash of the online agent set in Redis, surviving restarts
and working correctly across replicas
`BYPASS_MODEL_ACCESS_CONTROL=False` must be set on Open WebUI to enforce these access controls.
## Chat toggles for agents
For an agent, Open WebUI's Web Search, Code Interpreter and Image Generation toggles are a request to the agent, not
something Open WebUI runs itself. The agent decides what to do and runs it as a traced, cost-attributed step.
- **Which toggles appear**: an agent shows a toggle only when its blueprint supports that feature. Support comes from
the capabilities the blueprint installs, and the provisioner writes it onto the agent's model in Open WebUI. Plain LLM
models keep all of Open WebUI's own toggles.
- **What happens when a toggle is on**: an AI-Hub filter attached to agent models takes the toggles out of the request
before Open WebUI acts on them, and hands the supported ones to the agent. Open WebUI runs none of its own search,
image generation or code interpreter for agents, and does not change the prompt the agent receives.
- **Stale toggles**: Open WebUI sends a toggle's state even after the user switched to a model that hides it. The filter
forwards only features the selected agent supports, and the agent ignores any other feature an API caller sends.
- **Memory**: Open WebUI's own memory injection is off for agents, which recall user and organization memory themselves.
## Attached files in agent chats
When a user attaches a file in an agent chat, the agent reads it; Open WebUI only stores the upload.
- **Open WebUI does not process it for agents**: file retrieval and injection (the File Context capability) are off on
agent models, and Open WebUI no longer embeds uploads, since it runs in full-context mode and never read the
embeddings. Plain LLM models keep Open WebUI's own file handling.
- **The agent reads the whole document**: the pipe copies each file into the agent's upload bucket, once per agent, and
the agent parses it with the platform's document extraction (MinerU for PDFs and images, MarkItDown for Office). The
full text goes into the model's context, trimmed to fit, and the user is told when only part of a file fit.
- **Later turns and edited messages**: every file of the current message branch is sent on every turn, so a file
attached earlier keeps answering later questions, and an edited or regenerated message sees only its branch's files.
- **Sources**: each file the model read is listed as a source that opens the original upload and shows the text the
model received. A file that cannot be read is reported instead of silently skipped.
- **Temporary chats**: Open WebUI stores nothing and sends the text it extracted in the browser; the agent reads that
text like any other attachment.
## Inline citations
Agent answers link each statement to the source it came from, the way Open WebUI does for its own retrieval.
- **What the model cites**: every knowledge document and attached file in the prompt carries a short id, and the model
cites it as `[s3f9a1c]`. The ids stay stable across turns and retrievals because they derive from the document itself,
not from its position in a list.
- **What Open WebUI shows**: Open WebUI links only numbers, counted over the sources of the message. The pipe lists each
document once, in the order the agent handed them over, and rewrites every cited id into that document's number while
the answer streams. An id the agent never listed as a source is dropped instead of shown as a dead marker.
- **Which documents are listed**: the documents the model actually read, after reranking and including documents carried
over from an earlier turn, not every search hit. Each is labelled with its title or file name, and links to the
document when its source provides a reference URL.
## Referencing our knowledge with `#`
In an agent chat, typing `#` lists the knowledge collections the user may read, named "Database / Collection", next to
Open WebUI's own knowledge bases. Open WebUI stays a picker: the entries hold no content and it runs no retrieval on
them. The agent searches the referenced collections itself, with the embedding model each database was indexed with, and
cites what it finds like any other source.
- **Who sees what**: an entry is readable by the role groups that may browse the collection, so the list matches the
knowledge area. The agent checks the asking user's access again before it searches, so a reference never reaches a
collection the user may not read. A collection that is unreadable or deleted is named to the model, which tells the
user it could not use it.
- **Kept in sync automatically**: entries are created when a collection appears, a few seconds after a collection or
database changes, and on every discovery cycle for collections a pipeline creates. A collection that is gone loses its
entry.
- **On top of the agent's own retrieval**: a Document Intelligence Assistant searches its configured sources and the
referenced collections; an Instructed Assistant answers from the referenced collections alone.
## Tool calls in agent chats
When an agent lets the model choose its tools (see [Agents overview](../../5_agents/#tools-the-model-chooses)), every
call shows in Open WebUI as one of its own collapsible tool blocks, under a readable label such as "Search our
knowledge", with the result inside, as the model received it. Text the model streamed before a call moves into a
collapsed thought, so the answer starts where the work ends. While the agent gathers, the chat shows what it is doing,
including when a long conversation is being condensed to fit the model.
- **Approvals**: a tool an admin marked for approval asks a yes/no confirmation in the chat that names the tool and what
the call does. A declined tool is not requested again for that answer, and the model answers without it.
- **Toggles decide what is offered**: a tool that needs Code Interpreter or My Files is offered only while that toggle
is on. See [Chat toggles for agents](#chat-toggles-for-agents).
- **Limits**: a profile caps how often the model may decide and how many calls it may make. At the cap the model answers
with what it has and says it stopped early.
- **Sources**: a knowledge search the model chose produces the same citation chips as a `#` reference.
## Chat disclaimer
A short note below the chat input, such as "AI can make mistakes. Please verify answers.", is managed per tenant by
system administrators in the SysAdmin UI under **Tenants → Overview**. It can be set in German, English, French and
Italian, up to 100 characters each, and follows the user's language; a tenant without custom text shows the translated
default. Open WebUI has no setting for this, so the platform shows the text through a small script Open WebUI loads. The
deployment details are in `infra/deployment/openwebui-disclaimer.md`.
## Configuration and deployment
Open WebUI deploys as an independent Docker container within the platform. This provides isolation while managing the
lifecycle - starting, stopping, and updating - alongside other services.
The chat container accesses platform infrastructure like databases, object storage, and message queues through standard
patterns. Chat data persists with other platform data, supporting unified backup and data governance.
Configuration parameters propagate through environment variables and configuration files. Authentication endpoints,
model access URLs, and feature toggles stay consistent across development, testing, and production environments.
The platform tests new Open WebUI releases in isolated environments before production deployment. This protects against
breaking changes while providing access to improvements.
## Extension points
The integration preserves Open WebUI's core functionality but adds platform-specific enhancements.
The PostMessage protocol extends chat capabilities beyond native features. Custom message types trigger platform
workflows or data displays without modifying the open-source codebase.
The platform can overlay UI elements like notification badges or quick-action buttons without modifying Open WebUI.
These enhance functionality while keeping updates simple.
API calls between the chat interface and backend services can be intercepted to add context, enrich responses, or
enforce governance.
Platform theme settings apply through CSS customization rather than source code modification. This maintains visual
consistency with the suite's design.
## Monitoring
The platform monitors Open WebUI container health through standard endpoints. Service failures trigger automatic
recovery or administrator alerts.
Usage metrics - conversation counts, response times, error rates - flow to platform observability systems.
Administrators monitor chat service performance alongside other metrics.
Chat logs aggregate with platform logs in unified infrastructure. This supports troubleshooting across multiple
components.
Resource consumption monitoring - CPU, memory, network - supports capacity planning as user populations and conversation
volumes grow.
## What this approach provides
Open WebUI and the platform evolve independently. New releases integrate through standard processes without platform
code changes. Platform enhancements don't require chat interface modifications.
Responsibilities stay clear. Open WebUI handles chat interactions. The platform provides authentication, authorization,
knowledge management, and agent orchestration. This separation simplifies testing and maintenance.
Embedding rather than forking preserves open-source advantages. The platform receives community contributions, security
patches, and features without maintaining a custom variant.
Organizations can replace Open WebUI with alternative chat interfaces using the same embedding and messaging patterns.
This avoids lock-in to specific chat technology.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/12_strategic_rationale.md
title: "Strategic rationale"
description: ""
---
# Strategic rationale
The Swiss AI Hub integrates Open WebUI rather than developing a custom chat interface. This section explains the
reasoning behind this approach.
## Build vs. integrate
When developing an enterprise AI platform, teams decide which components to build from scratch and which to adopt. Chat
interfaces for AI interactions have become commodity functionality. Dozens of open-source projects and commercial
products provide sophisticated chat experiences. While user experience details vary, the core functionality - message
exchange, conversation history, multi-model support - is well-understood.
Building a production-grade chat interface requires substantial investment - user interface design, accessibility
implementation, mobile responsiveness, keyboard navigation, rich text rendering, file handling, and continuous feature
enhancement. This investment doesn't differentiate the Swiss AI Hub from alternatives.
By adopting Open WebUI, the development team concentrates resources on capabilities that genuinely differentiate the
platform - enterprise knowledge management, transparent agent workflows, process automation, multi-language support,
Swiss data sovereignty compliance.
Organizations evaluating the platform gain immediate access to comprehensive chat functionality without waiting for
custom development cycles.
## Open source advantages
Open WebUI benefits from contributions by a global developer community. Feature enhancements, bug fixes, security
patches, and usability improvements flow from this community effort without requiring Swiss AI Hub development
investment.
As an established open-source project, Open WebUI maintains compatibility with industry-standard AI APIs, model formats,
and integration patterns. This compatibility ensures the Swiss AI Hub can leverage emerging AI technologies without
waiting for proprietary interface vendors.
Open-source code enables organizations to audit chat interface implementation, verifying security properties, data
handling practices, and compliance with requirements. This transparency supports trust and addresses concerns impossible
to resolve with closed-source commercial products.
Organizations deploying the Swiss AI Hub can fork, modify, or extend Open WebUI if requirements exceed standard
capabilities. Organizations aren't constrained by vendor feature roadmaps or commercial product limitations.
Open-source adoption eliminates per-user licensing fees, API call charges, or usage-based pricing common with commercial
chat products. Organizations pay infrastructure costs only.
## Risk management
Open WebUI has extensive production deployments across diverse organizations. Bugs, edge cases, and failure modes have
been encountered, reported, and addressed through community maintenance.
The project's security posture reflects community scrutiny and vulnerability disclosure processes. Security researchers
examine open-source code, report vulnerabilities, and verify fixes.
Open WebUI's active community maintains comprehensive documentation, troubleshooting guides, and discussion forums.
Organizations encountering issues benefit from this community knowledge base.
As an actively maintained open-source project with healthy contributor diversity, Open WebUI demonstrates sustainability
indicators suggesting long-term viability.
## Integration effort vs. custom development
Embedding Open WebUI into the Swiss AI Hub suite required developing iframe integration patterns, PostMessage
communication protocols, authentication coordination, and deployment orchestration. This integration effort represents
weeks of development work.
Building equivalent chat functionality from scratch would require months of full-stack development - frontend
implementation, backend infrastructure, testing, accessibility compliance, mobile optimization, documentation.
Custom chat interfaces require continuous maintenance - bug fixes, security patches, browser compatibility updates,
feature enhancements. Open WebUI integration shifts this maintenance burden to the community while the Swiss AI Hub team
maintains only integration points.
The integration approach delivers comprehensive chat functionality for a fraction of custom development costs.
## Extensibility without forking
The Swiss AI Hub extends chat functionality through integration points - PostMessage communication for source
attribution and trace display - rather than modifying Open WebUI code. This approach enables adopting new Open WebUI
releases without merge conflicts or custom code maintenance.
Enhanced source attribution and execution tracing complement rather than replace Open WebUI features. Users gain both
comprehensive chat functionality and enterprise transparency capabilities.
If future requirements exceed Open WebUI's capabilities or community direction diverges, the integration architecture
enables replacing the chat component without platform-wide changes.
Improvements the Swiss AI Hub team makes to Open WebUI can be contributed back to the community, benefiting other
deployments while improving the project for all users.
## What this provides organizations
Organizations gain production-grade chat functionality from day one of deployment, without waiting for feature
development or paying for custom development.
As Open WebUI evolves, organizations benefit from new features, performance improvements, and bug fixes through standard
platform update cycles.
IT teams manage a single integrated platform rather than coordinating multiple chat products, model APIs, knowledge
bases, and analytics tools.
By building on proven open-source foundations rather than proprietary technologies, organizations protect their AI
platform investment against vendor discontinuation, pricing changes, or strategic pivots.
The integration strategy demonstrates technical maturity - recognizing when to build, when to buy, and when to integrate
open-source solutions.
## Competitive positioning
Competitors choosing custom chat development invest months achieving feature parity the Swiss AI Hub gained through
integration. This time advantage enables focus on genuinely differentiating capabilities.
Organizations comparing total cost of ownership find the Swiss AI Hub competitive with or superior to platforms
requiring separate chat product licensing, custom development fees, or ongoing maintenance contracts.
The integration approach demonstrates architectural flexibility - the Swiss AI Hub can adopt best-of-breed solutions
when they provide superior value.
Active participation in the open-source ecosystem through Open WebUI integration signals the Swiss AI Hub's commitment
to open standards, community collaboration, and sustainable technology choices.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/13_file_generation.md
title: "File generation"
description: ""
---
# File generation
Ask a model to produce a file — a report, a spreadsheet, a chart, a slide deck — and it writes that file inside the
**Open Terminal** sandbox using Python. It then opens the finished file in your file viewer and confirms the filename;
the file also stays reachable through the terminal panel's file browser.
You do not need to write or read any code to use this. Describe the document you want and let the model produce it.
::: warning Requirements
File generation runs on the same code-execution path as [Coding / Software Development](../6_coding/), so it inherits
that path's constraints: it works only with **plain LLM models that support function (tool) calling**, the model must
use **Native Function Calling** (Kimi-K2.6 does by default; for another model, switch it in the chat's Controls →
Advanced Params), **a terminal has to be selected in the chat** — the sandbox tools are resolved only then — and AI-Hub
agents other than the [Universal Agent](../../5_agents/13_universal_agent/) do not run code. See that page for the
sandbox mechanics, the per-user isolation model, and the fact that generated files are kept indefinitely.
:::
::: tip Recommended model — Workspace → Kimi-K2.6
Select **Kimi-K2.6** in the model picker (under **Workspace**) when you want the model to produce a file.
File generation is a multi-step tool-calling loop: the model has to write correct Python, run it with `run_command`,
read the result back, and then hand the file over with `display_file`. Reliability on that loop varies far more between
models than the list of supported formats does — every AI-Hub text-generation model declares function-calling support,
but declaring it is not the same as carrying a multi-step build through to a finished file. Kimi-K2.6 is the model the
format matrix below was verified against. If another model answers without producing a file, try Kimi-K2.6 before
concluding that the format is unsupported.
Kimi-K2.6 uses **Native Function Calling** by default. Any other model needs it switched on in the chat's Controls →
Advanced Params.
:::
## Supported output formats
What you can ask for. Verified against the `open-terminal-office:0.11.34` sandbox image — "Yes" means you get a real,
working file; "Source only" means you get the diagram's text source rather than a picture; "No" means it cannot be done.
This says nothing about whether the same format can be read back in as an upload.
| Category | File formats | Can be generated |
| ------------------ | ------------------------------------------------------- | ---------------- |
| Text documents | DOCX, RTF, TXT, Markdown (`.md`), HTML | Yes |
| Portable documents | PDF | Yes |
| Spreadsheets | XLSX, XLS, CSV | Yes |
| Presentations | PPTX, PDF | Yes |
| Images (raster) | PNG, JPG/JPEG, TIFF, WebP, BMP | Yes |
| Video | MP4, WebM, MOV, GIF | Yes |
| Audio | MP3, WAV, OGG, FLAC | Yes |
| Source code | py, cs, java, ts, js, go, rs, cpp, sql, yaml, json, xml | Yes |
| Data exchange | JSON, XML, YAML, CSV | Yes |
| Knowledge bases | Markdown, HTML, Confluence storage format, MediaWiki | Yes |
| Diagrams (source) | SVG, Draw.io (`.drawio`), Mermaid, PlantUML | Source only |
| Diagrams (Visio) | Visio (`.vsdx`) | No |
## Known limitations
::: warning
- **Diagrams are generated as source, not as pictures.** SVG, Draw.io, Mermaid and PlantUML files are text or XML, so
the sandbox writes them without trouble — but it ships **no renderer** for them (no `cairosvg`, `mmdc`, `plantuml`,
Graphviz or Inkscape), so it cannot turn them into an image file. Ask for a raster image via `matplotlib` instead when
you need a picture rather than a source file. Note that Mermaid returned *in a chat response* is a separate path: the
chat UI renders that inline as a diagram, which is why a Mermaid answer can look rendered while a Mermaid **file**
stays source.
- **Visio (`.vsdx`) cannot be generated.** There is no Visio library in the sandbox and no LibreOffice to convert
through. Ask for `.drawio` or SVG source instead.
- **Reading a format is not the same as writing it.** TIFF and Visio files could not be read back into the sandbox as
input during capability testing, even though TIFF generation works.
- **The format list is tied to the sandbox image.** It reflects `open-terminal-office:0.11.34`. Bumping that tag can add
or remove libraries, and therefore formats, without any other visible change.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/10_chat_ui/14_my_files.md
title: "My Files"
description: ""
---
# My Files
**My Files** is your own file space: every file you attach in an agent chat, every file an agent makes for you, and
everything you upload yourself, in one place. You can open, download, upload, rename, move into folders and delete
them, and agents can read them when you let them.
The files live in your home in the code sandbox, the same place the [Universal Agent](../../5_agents/13_universal_agent/)
runs code and OpenWebUI's terminal works. Nobody else sees them.
## Where to find it
- **On its own**: open **My Files** in the app menu.
- **Next to a chat**: click the folder icon under an agent's answer. My Files opens beside the chat, at that chat's
folder.
## How it is organised
- **`conversations/`** holds a folder per agent chat, shown with the chat's title. Every file you attach in that chat
lands there, and so do files an agent made in it.
- Everything else is yours to organise: create folders and move files into them.
Files whose names start with a dot are hidden; they belong to the sandbox and its tools.
## What you can do
| Action | How |
| ------------------ | ---------------------------------------------------------------------------------------- |
| Open a folder | Click it; the path above the list takes you back. |
| Preview a file | Click it. Text, images and PDFs show beside the list. |
| Download | The download icon. Every file downloads exactly as stored, office documents included. |
| Upload | **Upload**, or drop files onto the list. A file with the same name is replaced. |
| New folder | **New folder**. |
| Rename | The pencil icon. |
| Move | The folder icon: into a folder in the current one, or up one level. |
| Delete | The bin icon. A folder is deleted with everything in it. |
Changes take effect right away: code an agent runs afterwards sees them.
## Letting an agent read your files
In a chat with the Universal Agent, switch on **My Files** in the chat's toggles. The agent can then list your folders
and read your files, including PDF and office documents as text, to answer you. It never changes or deletes them; to
create files, it uses [Code Interpreter](../6_coding/).
## Good to know
- **Your file space is set up with your chat account.** If My Files says it is not set up yet, open the chat once.
- **Files are kept.** They are copied to the platform's storage every few minutes; a file you delete is kept there for a
week before it is gone.
- **One shared sandbox.** Each user's home is separate, but all users share one sandbox container; see
[Coding](../6_coding/) for what that means.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/11_access_management.md
title: "Access Management"
description: ""
---
# Access Management
This section covers access control and user management in the Swiss AI Hub platform.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/11_access_management/1_authentication_setup.md
title: "Authentication Setup"
description: ""
---
# Authentication Setup
Swiss AI Hub uses a multi-tenant authentication and authorization system with local role management.
## Overview
The authentication system consists of several key components:
- **Auth Handlers**: Validate credentials and resolve user identity
- **Identity Models**: `UserIdentity` and `TenantIdentity` represent authenticated users and their tenant context
- **Access Control**: `AccessChecker` enforces permissions based on hierarchical access rules
- **Multi-Tenancy**: All operations occur within a tenant context
## Authentication Flow
### 1. Token Validation
Auth handlers validate incoming requests and extract user information:
```python
# Keycloak OIDC JWT validation
user_identity = await KeycloakAuthHandler()(request)
# Token-based authentication
user_identity = await TokenAuthHandler()(request)
```
Supported authentication methods:
- **OAuth2/OIDC**: JWT tokens from Keycloak (supports federated identity providers like Azure AD, Google, etc.)
- **API Tokens**: Long-lived tokens for programmatic access
- **OpenWebUI Integration**: Special handler for OpenWebUI users
For tests and interactive playground servers, a dedicated `TestAuthHandler` lives under
`swiss_ai_hub.core.testing.auth_utils` (not `core.auth`) and bypasses token parsing to return a fixed test identity. It
is deliberately not reachable from production code via the public auth interface.
### 2. User Resolution
User profile data (name, email) is read from JWT claims for OAuth2 flows or fetched via `KeycloakAdminService` for
bearer tokens. There is no local user record — Keycloak is the single source of truth for user identity.
**First User Behavior**: The first user to join a tenant automatically receives admin roles. Subsequent users receive
standard user roles (configurable via `UserSignupSettings`). This applies per-tenant, not globally, and is enforced in
`UserTenantRoleEntity` when a new membership is created.
### 3. Tenant Context Resolution
Most authenticated requests have a tenant context. The tenant is identified via a `{tenant_id}` path parameter in the
URL — most API routes are mounted at `/api/v1/{tenant_id}/...`. Sysadmin-only endpoints (e.g. the tenant administration
controller) are mounted globally without a tenant prefix.
Tenant resolution is handled inside `AuthHandler.build_identity()` and `AuthHandler._resolve_tenant_by_id()`. The latter
consults `KeycloakAdminService.tenant_exists()` first — Keycloak is the authority on whether a tenant exists — and only
then checks `UserTenantRoleEntity` membership. The membership check is skipped entirely for sysadmins (see "Sysadmin
access" below). Controllers do not call these resolvers directly; they are wired into `user_with_permission()` and
`sys_admin_user()`.
**Tenant Path Parameter**: All API requests must include the `{tenant_id}` in the URL path. Two formats are supported:
- **Concrete ID**: `/api/v1/507f1f77bcf86cd799439011/agents/...` — directly specifies the tenant by MongoDB ObjectId
- **Active slug**: `/api/v1/active/agents/...` — resolves to the user's persisted active tenant
The active tenant is never automatically updated during request resolution. It can only be changed via a dedicated API
endpoint. Health endpoints remain outside tenant scope at `/api/v1/health/`.
### 4. UserIdentity Construction
Auth handlers return a `UserIdentity` that includes both user and tenant information:
```python
return UserIdentity(
id=user.id,
name=user.name,
email=user.email,
roles=roles,
acting_within_tenant=tenant, # may be None for sysadmin-only requests
is_sys_admin=is_sys_admin, # derived from the AIHubSysAdmin Keycloak realm role
)
```
The `is_sys_admin` flag is the single signal for platform-admin status — it short-circuits the access checker (see
"Sysadmin access" below) and is the basis for the `Controller.sys_admin_user()` dependency that gates sysadmin-only
endpoints.
## Multi-Tenant Role Management
### Core Entities
**TenantMetadataEntity**
- Holds display metadata (name, description, access rules) for a tenant
- **NOT** the source of truth for tenant existence — the Keycloak group `/tenants/` is authoritative. Service code
must verify existence via `KeycloakAdminService.tenant_exists()` before trusting metadata.
- Contains `access_rules` that cap what ANY user in the tenant can access
- Example: `["aihub.user.agent.>"]` grants user-level access to all agents
**UserTenantRoleEntity**
- Maps users to tenants with specific roles
- Authoritative source for user-tenant-role relationships
- Users can have different roles in different tenants
**RoleEntity**
- Every role belongs to exactly one tenant — `tenant_id` is required
- The default role set (`AIHubUser`, `AIHubAdmin`, `AIHubAgentUser`, etc.) is seeded per tenant at creation time
- System-wide roles no longer exist; see ADR `2026_04_14_tenant_scoped_roles.md`
**User profile data**
- Stored in Keycloak, not locally — `KeycloakAdminService.get_user_by_id()` / `find_user_by_email()` for lookup
- Name, email, and identity attributes all flow from Keycloak; the platform writes nothing to user records
- Roles are NOT attached to the user record — they are fetched from `UserTenantRoleEntity` per tenant
### Accessing User Roles
```python
# Get user's roles in a specific tenant
roles = user.get_roles(tenant_id)
# Get all access rules for a user in a tenant
access_rules = RoleEntity.get_access_rules_for_roles(roles, tenant_id=tenant_id)
```
## Access Control
### AccessChecker
The `AccessChecker` class performs authorization checks with two-stage access control:
```python
from aihub_lib.auth.access.AccessChecker import AccessChecker
# Create checker from UserIdentity (includes tenant context)
checker = AccessChecker.from_user(user)
# Check access level
level = checker.access_level("aihub.user.agent.class-a.id-123")
# Returns: AccessLevel.ACCESS_ADMIN | ACCESS_USER | ACCESS_DENIED
```
### Two-Stage Access Checking
**CRITICAL**: Tenant access rules act as a CEILING/BOUNDARY for user permissions.
1. **STAGE 1**: Determine tenant's access level (admin or user)
2. **STAGE 2**: Determine user's access level (admin or user)
3. **STAGE 3**: Return MINIMUM of both levels
**Example**:
```python
# Tenant has: aihub.user.agent.> (user-level access to all agents)
# User has: aihub.admin.agent.> (admin-level access to all agents)
# User gets ACCESS_USER (capped by tenant boundary)
checker.access_level("aihub.user.agent.class-a.id-1") # → ACCESS_USER
```
### Access Rule Format
Access rules follow a hierarchical dot-notation:
```
aihub.[admin|user]...
```
**Wildcards**:
- `*` - Single-level wildcard: `aihub.user.agent.*` matches any single agent
- `>` - Multi-level wildcard: `aihub.user.agent.>` matches all agents and sub-resources
**Examples**:
```python
"aihub.admin.>" # Full admin access to everything
"aihub.user.>" # Full user access to everything
"aihub.user.agent.>" # User access to all agents
"aihub.user.agent.class-a.*" # User access to all class-a agents
"aihub.user.agent.class-a.id-123" # User access to specific agent
```
### Convenience Methods
```python
# Check specific agent access
has_access = checker.has_access_to_agent("class-a", "id-123")
access_level = checker.access_level_for_agent("class-a", "id-123")
# Check agent class access
has_access = checker.has_access_to_agent_class("class-a")
# Check process access
has_access = checker.has_access_to_process("workflow", "proc-456")
# Check service access
has_access = checker.has_access_to_service("llm-gateway")
```
## Configuration
### Environment Variables
```bash
# Startup Tenant Configuration (seeded on first boot; an ordinary tenant thereafter)
AIHUB_STARTUP_TENANT_NAME="Swiss AI Hub"
AIHUB_STARTUP_TENANT_DESCRIPTION="This tenant was auto-created on startup of the Swiss AI Hub."
AIHUB_STARTUP_TENANT_ACCESS_RULES="aihub.admin.>"
# User Signup Role Assignment
AIHUB_USER_SIGNUP_DEFAULT_ROLES="AIHubUser"
AIHUB_USER_SIGNUP_REGULAR_USER_ROLES="AIHubUser"
AIHUB_USER_SIGNUP_FIRST_ADMIN_USER_ROLES="AIHubAdmin,AIHubUser"
# OAuth2 Configuration
OAUTH2_ENABLED=true
OAUTH2_JWKS_URL="https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys"
OAUTH2_ISSUER="https://login.microsoftonline.com/{tenant}/v2.0"
OAUTH2_AUDIENCE="api://{app-id}"
```
### Settings Classes
```python
from swiss_ai_hub.core.infrastructure.api.startup_tenant_settings import StartupTenantSettings
from swiss_ai_hub.core.infrastructure.api.user_signup_settings import UserSignupSettings
# Access startup tenant settings
tenant_settings = StartupTenantSettings()
print(tenant_settings.access_rules_list) # ['aihub.admin.>']
# Access user signup settings
signup_settings = UserSignupSettings()
print(signup_settings.first_admin_user_roles_list) # ['AIHubAdmin', 'AIHubUser']
```
## Best Practices
### 1. Always Provide Tenant Context
Never create `AccessChecker` without tenant access rules:
```python
# ❌ BAD: Manual construction without tenant context
checker = AccessChecker(user_access_rules, [])
# ✅ GOOD: Use factory method that extracts both user and tenant rules
checker = AccessChecker.from_user(user) # user is UserIdentity with tenant context
```
### 2. Verify Tenant Membership
Always verify users have access to the tenant before performing operations:
```python
roles = UserTenantRoleEntity.get_roles_for_user_in_tenant(user_id, tenant_id)
if not roles:
raise HTTPException(403, "User not assigned to tenant")
```
### 3. Use the Minimum Required Access Level
Grant users the minimum access level needed for their role:
```python
# ❌ BAD: Over-permissive
access_rules = ["aihub.admin.>"]
# ✅ GOOD: Scoped to specific resources
access_rules = ["aihub.user.agent.class-a.>"]
```
### 4. Tenant Access Rules as Boundaries
Use tenant access rules to limit the scope of ALL users in a tenant:
```python
# Tenant for analytics team - only access to specific agent classes
tenant_access_rules = [
"aihub.user.agent.analytics.*",
"aihub.user.service.data-pipeline"
]
# Even admin users in this tenant cannot access other resources
```
## Troubleshooting
### "User not assigned to tenant" Error
**Cause**: User exists but doesn't have any roles in the requested tenant.
**Solution**: Assign user to tenant with roles:
```python
UserTenantRoleEntity.create_or_update(
user_id=user_id,
tenant_id=tenant_id,
roles=["AIHubUser"]
)
```
### "Access Denied" Despite User Having Admin Roles
**Cause**: Tenant access rules are limiting user permissions. (Sysadmins — users with the `AIHubSysAdmin` Keycloak realm
role — bypass this check entirely; if the issue persists for a sysadmin, the bypass itself is misconfigured.)
**Solution**: Check tenant access rules:
```python
tenant = TenantMetadataEntity.get_metadata_by_tenant_id(tenant_id)
print(tenant.access_rules) # Check what the tenant allows
```
### Empty Tenant Access Rules = No Access
If a tenant has no access rules (`[]`), ALL users in that tenant are denied access to everything.
**Solution**: Set appropriate tenant access rules:
```python
tenant.access_rules = ["aihub.user.>"]
tenant.save()
```
## Security Considerations
- **Never mount `TestAuthHandler` on production entry points** — it lives under `core.testing` for this reason;
production `app/main.py` files must use `KeycloakAuthHandler` or `TokenAuthHandler`
- **Validate JWTs properly** - always verify issuer, audience, and signature
- **Use HTTPS** - never transmit tokens over unencrypted connections
- **Rotate API tokens regularly** - implement token expiration and rotation
- **Audit access control changes** - log all role and permission modifications
- **Principle of least privilege** - grant minimum required access
- **Tenant isolation** - users cannot access resources outside their tenant's boundaries
## Migration from Previous System
Previous versions fetched roles from Azure AD via Microsoft Graph API. The new system:
- ✅ **Stores roles locally** in `UserTenantRoleEntity`
- ✅ **No external API calls** during authentication
- ✅ **Tenant-scoped roles** for multi-tenancy
- ❌ **No automatic role sync** from identity provider
- ❌ **No automatic profile image fetching** from identity provider
See [ADR: Local Multi-Tenant Role Management](/arc42/decisions/2025_12_25_local_role_management.md) for details.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/11_access_management/2_permissions.md
title: "Permission and Access Control"
description: ""
---
# Permission and Access Control
The Swiss AI Hub suite interface implements sophisticated permission-based access control that dynamically adapts the
user experience to each individual's authorization level. This approach ensures users see only relevant capabilities
while maintaining security and compliance requirements.
## Dynamic Service Visibility
Traditional application interfaces often present all features to all users, relying on authentication checks to block
unauthorized access. This creates cluttered interfaces filled with disabled buttons and features users cannot use,
leading to confusion and support burden. The Swiss AI Hub fundamentally reimagines this approach through dynamic service
visibility.
**Permission-Filtered Service Catalog**: When the suite loads, it queries the backend for the user's authorized service
catalog. The backend evaluates the user's permissions against each registered service's requirements, returning only
services the user can access. The interface renders navigation elements exclusively for authorized services - users
simply never see capabilities they cannot use.
**Clean, Focused Interface**: This approach creates dramatically simpler interfaces compared to traditional
applications. A data scientist sees evaluation and experimentation services prominently featured. A business analyst
sees conversation threads and knowledge exploration tools. An administrator sees user management and system
configuration options. Each user's interface reflects their actual capabilities, not a universal feature set cluttered
with inaccessible options.
**Automatic Permission Updates**: When an administrator modifies a user's role assignments or permission grants, these
changes automatically reflect in the user's interface upon their next session. There is no cache invalidation, manual
refresh, or logout-login cycle required. The suite's architecture ensures the interface always presents an accurate view
of current authorization state.
**Security Through Invisibility**: By not rendering unauthorized service navigation elements, the suite eliminates an
entire class of security vulnerabilities. Users cannot attempt to access restricted services through interface
manipulation because those services have no interface presence. This defense-in-depth approach complements backend
authorization enforcement.
## Hierarchical Permission System
The suite integrates with the Swiss AI Hub's comprehensive hierarchical permission system, which provides fine-grained
access control through a structured, dot-notation permission syntax.
**Permission Structure**: Permissions follow the format `aihub.[user|admin]...`,
creating a hierarchical namespace that enables precise access control. For example,
`aihub.user.agent.support_agent.instance_001` grants access to a specific agent instance, while `aihub.admin.knowledge`
grants administrative access to the entire knowledge management service.
**Wildcard Support**: The permission system supports sophisticated wildcards that enable flexible access control without
requiring explicit enumeration of every resource. The `*` wildcard matches any single path segment, while the `>`
wildcard matches any remaining path segments. This enables rules like `aihub.user.agent.>` to grant access to all agent
resources at any depth.
**Implicit Permissions**: Users with the implicit permission pattern `aihub.user.?>` gain access to all user-level
services without requiring explicit grants for each service. This simplifies permission management for standard users
while maintaining fine-grained control for specialized access patterns.
**Service-Level Access Control**: Each service controller declares minimum permission requirements for access. The suite
endpoint evaluates whether the user possesses these minimum permissions when constructing the service catalog. Services
requiring permissions the user lacks simply don't appear in the catalog response.
## Role-Based Interface Adaptation
Beyond simple show/hide logic, the suite implements role-aware interface adaptation that presents different views and
capabilities based on user authorization levels.
**Administrative Privileges**: When the suite endpoint evaluates permissions, it determines not just whether the user
can access a service, but whether they have administrative privileges for that service. This distinction is communicated
to the frontend, which can present additional administrative capabilities within that service's interface.
**Context-Aware Navigation**: The suite maintains awareness of the user's current authorization context. When viewing an
agent, the interface can determine whether the user has administrative access to that specific agent, presenting
administrative controls like configuration editing only when authorized. Standard users see read-only views of the same
resources.
**Granular Feature Control**: Within individual services, the interface can query the user's permissions for specific
resources or operations. A user might have read access to knowledge bases but lack upload permissions. The interface
reflects this by showing knowledge exploration features while hiding document upload controls.
**Multi-Tenant Isolation**: In deployments serving multiple organizational units or customer tenants, the permission
system ensures complete data isolation. Users see only services and resources belonging to their organizational context,
creating secure, isolated workspaces within a shared platform deployment.
## Permission Evaluation Architecture
The suite's permission-aware behavior results from sophisticated coordination between frontend queries and backend
evaluation logic.
**Backend Permission Evaluation**: All permission evaluation occurs on the backend, ensuring security enforcement cannot
be bypassed through client-side manipulation. The suite endpoint queries the permission system, evaluates access rules
against the user's roles and grants, and returns a pre-filtered service catalog. The frontend trusts this catalog
without performing its own permission logic.
**Access Checker Integration**: The backend employs an Access Checker component that encapsulates permission evaluation
logic. This component accepts a user identity and permission pattern, evaluates whether the user's access rules match
the pattern, and returns either boolean access decisions or detailed access level enumeration (denied, user access,
administrative access).
**Efficient Permission Queries**: Permission evaluation is optimized for performance through caching strategies and
efficient pattern matching algorithms. When the suite endpoint evaluates service visibility for a user, it performs
these evaluations in parallel rather than sequentially, ensuring responsive interface load times even with numerous
services.
**Audit Trail Generation**: Every permission evaluation generates audit log entries documenting what permissions were
checked, for which user, and what decision was made. This creates comprehensive audit trails supporting compliance
reporting and security forensics.
## Service-Specific Permission Patterns
Different services implement different permission patterns based on their functional requirements, demonstrating the
flexibility of the hierarchical permission system.
**Agent Service**: Implements per-agent access control where users might have access to specific agent instances but not
others. Permissions like `aihub.user.agent.customer_support.cs_001` grant access to a specific agent, while
`aihub.user.agent.customer_support.*` grants access to all instances of that agent class.
**Thread Service**: Controls access to conversation threads based on ownership and sharing rules. Users generally have
access to threads they created or participated in, with administrators having broader visibility for support and
monitoring purposes.
**Knowledge Service**: Implements namespace-based access control where permissions can be granted at the database level
(`aihub.user.knowledge.hr_documents`) or namespace level (`aihub.user.knowledge.hr_documents.policies`), with
hierarchical inheritance through the permission tree.
**My Files**: Guarded by the service's own rule rather than by a per-resource rule, so a tenant whose access ceiling
already covers the platform's user services covers My Files too. Whatever the rule, a user only ever reaches their own
files.
**Administrative Services**: Require explicit administrative permissions like `aihub.admin.users` or
`aihub.admin.roles`. These services never appear for users without administrative grants, creating a clear separation
between standard and administrative interfaces.
## User Experience Benefits
The permission-aware suite architecture delivers significant user experience and operational advantages.
**Elimination of Access Denied Errors**: Users never encounter "access denied" messages for visible interface elements
because unauthorized features simply don't appear. This eliminates a common source of user frustration and support
tickets in traditional enterprise applications.
**Reduced Interface Complexity**: By showing only authorized capabilities, the interface remains uncluttered and
focused. Users don't need to mentally filter visible-but-disabled features from available capabilities - everything they
see, they can use.
**Self-Service Access Understanding**: Users can immediately understand their authorized capabilities by observing what
appears in the suite navigation. There's no need to consult separate documentation or contact support to determine what
features they can access.
**Streamlined Onboarding**: New users see only the capabilities relevant to their role, dramatically simplifying initial
platform orientation. Training can focus on relevant features rather than helping users understand what they cannot
access and why.
## Security and Compliance Advantages
The permission-aware architecture provides security and compliance benefits beyond user experience improvements.
**Defense in Depth**: Client-side filtering of unauthorized services complements backend permission enforcement,
creating multiple security layers. Even if an attacker manipulates the frontend, backend authorization enforcement
prevents unauthorized operations.
**Reduced Attack Surface**: By not exposing information about services users cannot access, the suite reveals less about
the deployment's capabilities to potential attackers. Users cannot probe disabled features to gather information for
attacks.
**Compliance Support**: The comprehensive audit logging of permission evaluations supports regulatory compliance
requirements for access control, particularly in sectors with strict data protection requirements like healthcare,
finance, and public administration.
**Zero-Trust Architecture**: The suite implements zero-trust principles where every service access requires explicit
permission evaluation. There are no implicit trust assumptions based on network location or previous authentication -
every operation is independently authorized.
## Operational Advantages
Beyond security and user experience, the permission system provides operational benefits for platform administrators.
**Centralized Permission Management**: Administrators manage permissions through the role management service, with
changes automatically reflected across the entire suite. There's no need to configure access controls separately for
each service or coordinate permissions across multiple systems.
**Flexible Delegation**: The hierarchical permission system enables sophisticated delegation patterns. Senior staff can
be granted broad access patterns like `aihub.user.agent.>`, while junior staff receive specific grants for individual
resources. This flexibility supports organizational structures without requiring complex access control configurations.
**Permission Inheritance**: The hierarchical structure enables permission inheritance where granting access to a
higher-level resource automatically provides access to contained resources. This simplifies permission management while
maintaining precise control when needed.
**Role-Based Administration**: Rather than managing individual user permissions, administrators typically assign users
to roles that define standard permission sets. Role modifications automatically apply to all assigned users, ensuring
consistent access control across user populations.
This permission-aware architecture ensures that the Swiss AI Hub suite provides each user with a focused, secure
interface precisely tailored to their authorization level and organizational role, while maintaining the operational
simplicity and security rigor required for enterprise and public sector deployments.
# Role-Based Access Control (RBAC)
## Overview
**Role-Based Access Control (RBAC)** is a security framework that restricts system access based on user roles within an
organization. The Swiss AI Hub implements a hierarchical RBAC system with tenant-scoped roles that provides granular
control over every aspect of the platform.
### Core Components
- **Roles**: Named collections of access rules that define what users can do (managed locally, not synced from identity
providers)
- **Tenants**: Organizational boundaries that scope role assignments
- **Access Rules**: Specific permissions using dot-notation (e.g., `aihub.admin.service.roles`)
- **User Identity**: Authenticated via OAuth2/OIDC, with roles resolved from the local tenant-scoped role database
- **Permission Templates**: Dynamic permission checking with path parameter substitution
- **Wildcard Support**: Flexible pattern matching using `*`, `>`, `?*`, and `?>` wildcards
### Permission Structure
The system uses a structured permission naming convention:
```
aihub.[user|admin].[resource_type].[resource_category].[resource_identifier]
```
**Examples:**
- `aihub.user.agent.customer_service.chatbot_v2` - User access to specific agent
- `aihub.admin.service.roles` - Admin access to role management
- `aihub.user.agent.?>` - User access to any agent (wildcard)
For multi-tenancy and access control details, see the
[Multi-Tenancy Access Control](../../16_multi_tenancy/4_access_control/) documentation.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/12_auditing.md
title: "Auditing & Observability"
description: ""
---
# Auditing & Observability
This section covers auditing, monitoring, and observability features of the Swiss AI Hub platform.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/12_auditing/1_high_level_interactions.md
title: "High-Level Interactions"
description: ""
---
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/12_auditing/2_low_level_traces.md
title: "Low-Level Traces"
description: ""
---
# Deep Observability with OpenTelemetry :telescope: :100:
::: info **TL;DR - What is Deep Observability?**
The Swiss AI Hub provides **end-to-end distributed tracing and deep observability** using OpenTelemetry standards,
giving you complete visibility into every aspect of your AI workflows. From individual agent steps to complex
multi-service processes, you can trace, monitor, and optimize every component of your AI ecosystem with enterprise-grade
observability that integrates seamlessly with industry-standard tools like Langfuse, SigNoz, or DataDog.
:::
## What is Deep Observability and How Does Swiss AI Hub Implement It? :brain:
**Deep Observability** goes far beyond traditional logging and monitoring. The Swiss AI Hub implements a comprehensive
observability strategy that combines **distributed tracing**, **semantic conventions**, and **AI-specific
instrumentation** to provide unprecedented visibility into your AI systems.
The platform uses **OpenTelemetry** as its foundational observability framework, enhanced with **OpenInference semantic
conventions** for AI/ML workloads. This means every interaction, from a simple user message to complex multi-agent
orchestrations, is automatically traced with rich contextual information including:
- **Complete Request Flows**: Follow a user request as it flows through APIs, agents, databases, and external services
- **AI-Specific Semantics**: Capture LLM calls, embeddings, retrievals, and model interactions with specialized semantic
attributes
- **Performance Metrics**: Track latency, token usage, cost attribution, and resource utilization across all components
- **Error Context**: Get detailed error traces with full context of what led to failures
- **Service Dependencies**: Automatically map how your services, agents, and processes interact in real-time
The system automatically instruments **every component** including NATS messaging, database operations, HTTP calls, LLM
interactions, vector searches, and custom agent workflows without requiring code changes.
## Why This is Critical for Enterprise AI Success :trophy:
Deep observability transforms how you build, debug, and scale AI systems in production:
**🔍 Complete System Visibility**: See exactly how your AI workflows execute in production, from user input to final
output, across all microservices and agents. No more blind spots in complex distributed AI systems.
**🚀 Performance Optimization**: Identify bottlenecks in your AI pipelines with precision. Know exactly which LLM calls
are slow, which retrievals are inefficient, and where your workflows can be optimized for speed and cost.
**🛡️ Proactive Issue Detection**: Catch problems before they affect users. Advanced tracing reveals patterns that lead
to failures, allowing you to fix issues proactively rather than reactively.
**💰 Cost Attribution and Control**: Track token usage, API calls, and compute costs down to individual users, agents, or
workflows. Make data-driven decisions about resource allocation and cost optimization.
**🌐 Vendor-Agnostic Flexibility**: OpenTelemetry ensures your observability data works with any OTLP-compatible backend.
Start with Langfuse for AI-specific analysis, then migrate to enterprise tools like DataDog or New Relic without losing
data or changing instrumentation.
::: details **Automatic Instrumentation Coverage**
The Swiss AI Hub automatically instruments these components without code changes:
### Core Infrastructure
- **NATS Messaging**: Complete message flow tracing across microservices
- **Database Operations**: FeretDB, ValKey, and vector database queries
- **HTTP Clients**: All external API calls and webhooks
- **Background Tasks**: Async operations and scheduled jobs
### AI-Specific Components
- **LLM Interactions**: Token usage, model calls, and response times
- **Embeddings**: Vector generation and similarity searches
- **Retrieval**: RAG operations and knowledge base queries
- **Agent Workflows**: Step-by-step execution traces with semantic context
:::
## Getting Started
To enable deep observability in your Swiss AI Hub deployment:
1. **Configure Environment Variables**: Set the OTEL configuration variables for your target observability backend
2. **Deploy with Tracing Enabled**: Restart your Swiss AI Hub services to activate automatic instrumentation
3. **Access Your Observability Dashboard**: View traces, metrics, and analytics in your chosen observability platform
The system requires no code changes - all instrumentation is automatic and follows OpenTelemetry standards for maximum
compatibility and minimal performance impact.
# Traces
## Overview
Traces follow individual requests through the Swiss AI Hub platform, showing the complete path from start to finish.
Each operation automatically receives a unique trace identifier that connects all related activities across services,
revealing exactly what happened, where time was spent, and how components collaborated.
The Swiss AI Hub uses OpenTelemetry for tracing with specialized support for AI operations through OpenInference
semantic conventions.
______________________________________________________________________
## What We Capture
### Agent Workflow Execution (Operational)
Agent runs are traced with hierarchical span structures showing the complete workflow:
**Agent Spans**: Root span marking the start of an agent execution with user input and agent identification.
**Chain Spans**: Long-running span capturing the complete run duration from start to final output.
**Step Spans**: Individual workflow steps showing inputs, outputs, processing time, and semantic events.
**Trace Attributes**:
- Session/thread identifiers for conversation context
- Input and output values in JSON format
- OpenInference span kinds (AGENT, CHAIN, TOOL, LLM, RETRIEVER)
- Tags for filtering (thread_id, display_id, run_id)
**Implementation**: The `AgentRunTracer` creates a CHAIN span for each workflow step, capturing inputs, outputs,
processing time, and semantic events. Langfuse trace-level attributes (name, session, user, input/output) are set via
span attributes so Langfuse groups all step spans into a single trace per run.
**Agent-in-the-Loop (AITL) Delegation**: When Agent A delegates to Agent B via AITL, the tracer creates a long-lived
AGENT wrapper span under Agent A's step. The wrapper span's context is propagated via Redis (using W3C TraceContext) so
Agent B's step spans re-parent under it. Agent B suppresses `langfuse.trace.*` attributes to avoid overwriting Agent A's
trace-level display. This produces a nested hierarchy in Langfuse where the delegated agent's steps appear under the
delegating agent's trace.
### AI Model Operations (Operational)
LLM operations are automatically traced through LlamaIndex instrumentation:
**LLM Invocations**: Model selection, prompt construction, token usage, and response generation.
**Retrieval Operations**: Vector database queries, document retrieval, and context assembly.
**Embeddings**: Text embedding generation for document indexing and similarity search.
**Semantic Events**: AI-specific operations emit semantic events containing detailed metadata (token counts, model
names, retrieved documents) that enrich traces with domain-specific information.
**Visibility**: All AI operations appear in the Langfuse tracing UI with specialized views for LLM performance analysis.
### HTTP and Database Operations (Operational)
Instrumented libraries automatically create spans for external service calls:
**HTTP Clients**: HTTPX and aiohttp requests with method, URL, status code, and timing.
**Databases**: FerretDB, PostgreSQL, and ValKey operations with query information.
**Vector Database**: Milvus similarity searches and indexing operations.
**Filtering**: Health checks, metrics endpoints, and high-volume database queries are filtered from traces to reduce
noise.
______________________________________________________________________
## Trace Collection Architecture
```mermaid
graph TB
subgraph Sources["Trace Sources"]
Agents[Agent Workflows
AgentRunTracer]
LLM[LLM Operations
LlamaIndexInstrumentor]
HTTP[HTTP Clients
HTTPX/AioHTTP]
DBs[Databases
Mongo/Redis/Milvus]
end
subgraph Collector["OpenTelemetry Collector"]
CloudPipeline[traces/cloud Pipeline
otlp → filter/noise → batch → cloud]
LangfusePipeline[traces/langfuse Pipeline
otlp → filter/langfuse → transform → batch → langfuse]
end
subgraph Backends["Trace Backends"]
Langfuse[Langfuse
LLM Observability]
Cloud[Cloud Backend
All Traces]
end
Agents --> CloudPipeline
Agents --> LangfusePipeline
LLM --> CloudPipeline
LLM --> LangfusePipeline
HTTP --> CloudPipeline
DBs --> CloudPipeline
CloudPipeline --> Cloud
LangfusePipeline --> Langfuse
style CloudPipeline fill:#e1f5ff
style LangfusePipeline fill:#fff4e1
```
### Collection Pipelines
The OpenTelemetry Collector processes traces through two specialized pipelines:
**traces/cloud**: Sends all traces to cloud backend
- Receiver: `otlp` (gRPC port 4317, HTTP port 4318)
- Processors: `filter/noise` (removes health checks, metrics endpoints, routine DB queries), `batch`
- Exporter: `otlp/cloud`
**traces/langfuse**: Sends AI-specific traces to Langfuse
- Receiver: `otlp` (gRPC port 4317, HTTP port 4318)
- Processors: `filter/langfuse` (keeps only OpenInference spans), `transform/langfuse` (adds project metadata), `batch`
- Exporter: `otlphttp/langfuse` (Langfuse OTEL ingestion endpoint, authenticated with `LANGFUSE_PUBLIC_KEY` /
`LANGFUSE_SECRET_KEY`)
### Instrumentation
Services automatically emit traces through OpenTelemetry instrumentation configured by `AihubInstrumentor`:
**Automatic Instrumentation** (via `AihubInstrumentor`):
- `AsyncioInstrumentor`: Async operations and task execution
- `HTTPXClientInstrumentor` / `AioHttpClientInstrumentor`: HTTP requests
- `PymongoInstrumentor` / `RedisInstrumentor` / `MilvusInstrumentor`: Database operations
- `LlamaIndexInstrumentor`: LLM and RAG operations with OpenInference conventions
**Custom Tracing** (via `AgentRunTracer`):
- Agent workflow execution with per-step CHAIN spans
- AITL delegation tracing with AGENT wrapper spans and Redis-based W3C context propagation
- Langfuse trace enrichment (name, session, user, input/output, token usage, model)
**Smart Tracing**: The `SmartTracer` respects `suppress_instrumentation` context, allowing selective tracing control.
______________________________________________________________________
## Business Benefits
### Performance Optimization
Traces reveal exactly where time is spent in each operation. Bottleneck identification becomes precise rather than
speculative. When document retrieval takes three seconds while AI processing takes 500ms, optimization priorities become
clear.
### Cost Management
AI operations include token usage and cost attribution through semantic events. Tracking which operations, users, or
departments consume the most AI resources enables data-driven decisions about model selection and feature pricing.
### Root Cause Analysis
Failed operations preserve complete context showing exactly where and why failures occurred. Error traces include stack
traces, input data, and the sequence of events leading to failure, dramatically reducing problem resolution time.
### AI Transparency
Traces show what information the AI considered when generating answers. Retrieved documents, token usage, and model
selection become visible, supporting regulatory compliance and building user trust.
______________________________________________________________________
## Accessing Trace Information
### Langfuse UI
Langfuse provides specialized LLM observability at `http://localhost:6006` (dev) or `https://langfuse.`
(production):
**Features**:
- Timeline views showing span duration and relationships
- Token usage and cost tracking per trace, user, and agent
- Retrieved document inspection for RAG systems
- Trace filtering by session, tags, or time range
- Performance analysis and latency distributions
- Dataset management and experiment evaluation
- Azure AD SSO integration for production access control
**Focus**: AI-specific operations with OpenInference semantic conventions (LLM, CHAIN, AGENT, RETRIEVER, EMBEDDING
spans).
### Cloud Backend (Production)
Traces are exported to cloud observability platforms for long-term storage and analysis. The platform supports any
OTLP-compatible backend through configuration changes only.
______________________________________________________________________
## Security and Privacy
### Trace Content
Traces capture operation metadata, timing information, and routing details. Developers are responsible for ensuring
sensitive data is not included in trace attributes.
**Infrastructure**: OpenInference spans include session IDs, model names, token counts, and retrieved document metadata.
**Application Responsibility**: Developers must avoid logging actual document content, user messages, or other sensitive
information in custom trace attributes.
### Transmission Security
All traces are transmitted via encrypted channels (TLS/HTTPS) to prevent interception.
### Access Control
Trace access is restricted through observability platform role-based access control. Only authorized personnel can view
detailed traces.
______________________________________________________________________
## Integration with Platform Components
### Agent Workflows
The `AgentRunTracer` creates a structured tracing hierarchy for agent executions:
1. Each workflow step gets a CHAIN span with inputs, outputs, and semantic event metadata
2. Langfuse trace-level attributes (name, session, user, tags) are set on step spans for grouping
3. Semantic events from AI operations enrich traces with token usage, model names, and LLM output
4. For AITL delegation, an AGENT wrapper span bridges Agent A's step to Agent B's step spans:
```
Trace: "AgentA/profile-1"
AgentA.start_step (CHAIN)
AITL -> AgentB/profile-2 (AGENT, wrapper span)
AgentB.compute_step (CHAIN, re-parented via Redis)
AgentB.stop_step (CHAIN, re-parented via Redis)
AgentA.end_step (CHAIN)
```
### LLM Operations
LlamaIndex instrumentation automatically traces:
- Language model invocations with token counts
- RAG operations showing document retrieval and context assembly
- Vector database searches and similarity operations
- Embedding generation for document processing
### HTTP Services
FastAPI services automatically trace incoming requests when instrumented. Developers can add custom attributes to spans
for application-specific context.
______________________________________________________________________
## Platform Flexibility
While Langfuse provides LLM-specific observability, the OpenTelemetry foundation supports any OTLP-compatible backend:
**Supported Platforms**:
- **Langfuse**: Open-source LLM observability with cost tracking and evaluation (current default)
- **SigNoz**: Open-source observability platform
- **Jaeger**: Distributed tracing focused on microservices
- **Tempo** (Grafana): Cloud-native distributed tracing
- **Datadog APM**: Commercial APM with comprehensive tracing
- **New Relic**: Application performance monitoring with AI insights
Switching backends requires only collector configuration changes. No application code modifications are needed.
______________________________________________________________________
## Future Development
### Planned Enhancements
**Tail Sampling**: Intelligent sampling that keeps error traces and interesting operations while reducing storage costs.
**Custom Business Events**: Higher-level traces for business operations beyond technical implementation details.
**Cost Prediction**: Pre-execution cost estimates based on historical trace data and query complexity.
**Performance Budgets**: Automatic alerts when operations exceed expected duration based on historical patterns.
______________________________________________________________________
## Summary
The platform's distributed tracing delivers:
✅ **Operational Agent Tracing**: Complete workflow execution with step-level detail through AgentRunTracer
✅ **AI Operation Visibility**: LLM and RAG operations traced with OpenInference semantic conventions
✅ **Automatic Instrumentation**: HTTP, database, and async operations traced without manual code
✅ **Dual Backend Support**: Langfuse for LLM-specific observability, cloud backend for full-stack production traces
✅ **Standards-Based**: OpenTelemetry ensures vendor flexibility through OTLP protocol
✅ **Performance Analysis**: Detailed timing information enables precise bottleneck identification
✅ **Privacy Foundation**: Infrastructure captures metadata; developers responsible for data protection
As tracing coverage expands, organizations gain increasingly detailed insights into platform performance, AI operations,
and user experience.
# OpenTelemetry Foundation
## Overview
**OpenTelemetry (OTel)** is the technical foundation for all observability in the Swiss AI Hub. It provides a
vendor-neutral, industry-standard framework for collecting, processing, and exporting telemetry data across metrics,
logs, and traces.
Unlike proprietary monitoring solutions that lock you into specific vendors, OpenTelemetry ensures the platform can
integrate with any compatible observability backend. This architectural decision provides organizations with maximum
flexibility in choosing monitoring tools based on their infrastructure, compliance requirements, and operational
preferences.
______________________________________________________________________
## Why OpenTelemetry?
OpenTelemetry lets us instrument services once and keep tool choice flexible. It standardizes metrics, logs, and traces
so signals correlate by default and switchable backends remain a config change, not a rewrite.
**Benefits**
- **Vendor-neutral by design:** Use any OTLP-compatible backend (e.g., SigNoz, Datadog, Grafana, Prometheus, New Relic)
without re-instrumentation.
- **Unified signals:** Consistent models and shared context (trace/span IDs, resource attributes) link metrics, logs,
and traces for faster troubleshooting.
- **Proven standard:** A CNCF project with broad industry support and active development, reducing technology risk.
- **Future-ready:** Evolve platforms and policies through the OTel Collector and configuration, not application code.
______________________________________________________________________
## OpenTelemetry Collector
The **OpenTelemetry Collector** is the central telemetry processing hub for the Swiss AI Hub.
### Architecture
```mermaid
graph TB
subgraph Sources["Telemetry Sources"]
Docker[Docker Socket
Container Stats]
HealthFiles[Health Event Files
NDJSON Logs]
Apps[Application Services
Instrumented with OTel SDKs]
end
subgraph Collector["OpenTelemetry Collector"]
direction TB
Recv[Receivers
• OTLP gRPC/HTTP
• docker_stats
• filelog]
Proc[Processors
• batch
• resource detection
• filter
• attributes]
Exp[Exporters
• otlp/signoz
• debug]
end
subgraph Backends["Observability Backends"]
SigNoz[SigNoz Cloud
Metrics, Logs, Traces]
Debug[Debug Output
Development]
end
Docker --> Recv
HealthFiles --> Recv
Apps --> Recv
Recv --> Proc
Proc --> Exp
Exp --> SigNoz
Exp --> Debug
style Recv fill:#e1f5ff
style Proc fill:#fff4e1
style Exp fill:#e8f5e9
```
### Components
**Receivers**: Collect telemetry from various sources.
**Processors**: Transform, enrich, filter, and batch telemetry before export.
**Exporters**: Send processed telemetry to observability backends.
**Extensions**: Provide auxiliary capabilities like health checks and profiling.
______________________________________________________________________
## Receivers
Receivers are intake points. They pull telemetry from apps and infrastructure into the platform.
- **OTLP receiver:** Standard entry for app telemetry. Services send metrics, logs, and traces using the OpenTelemetry
protocol. Concept: one wire format for everything.
- **Container metrics receiver:** Collects resource usage from running containers. Concept: observe runtime health
without touching app code.
- **File log receivers:** Ingest structured event logs like container and synthetic health checks. Concept: capture
operational signals even when apps lack native endpoints.
Outcome: Broad coverage with minimal coupling to any single tool or runtime.
______________________________________________________________________
## Processors
Processors shape telemetry in motion. They add context, reduce noise, and prepare data for analysis.
- **Batching:** Groups data for efficient transport. Concept: lower overhead without losing fidelity.
- **Resource detection:** Auto-enriches with environment details such as host, container, or system info. Concept:
attach who/where to every signal.
- **Attribute editing:** Normalizes tags like environment or source. Concept: consistent labels for reliable filtering
and dashboards.
- **Resource mapping:** Translates container facts into service identities (e.g., service name, version). Concept: align
infra reality with service views.
- **Filtering:** Drops low-value noise such as routine health checks. Concept: improve signal-to-noise and control cost.
Outcome: Clean, contextual, and analysis-ready telemetry.
______________________________________________________________________
## Exporters
Exporters deliver telemetry to destinations.
- **Primary backend exporter:** Sends data to the chosen OTLP-compatible platform. Concept: pick or change your analysis
tool without re-instrumenting.
- **Debug exporter:** Prints or previews data for validation. Concept: verify pipelines locally before scaling.
Outcome: Pluggable outputs with safe development workflows.
______________________________________________________________________
## Telemetry Pipelines
Pipelines are end-to-end flows per signal type. Each defines which receivers, processors, and exporters to use.
- **Metrics pipelines:** Optimize for throughput and trend analysis. Enrich with service context.
- **Log pipelines:** Preserve structure and order. Extract attributes for querying and correlation.
- **Trace pipelines:** Keep parent–child relationships intact. Batch carefully to maintain trace integrity.
Concept: purpose-built lanes that keep signals consistent and linkable across the stack.
______________________________________________________________________
## Extensions
Extensions add operational capabilities around the collector itself.
- **Health checks:** Expose collector status for monitoring. Concept: treat observability as a first-class service.
- **Profiling (pprof):** Inspect performance under load. Concept: diagnose pipeline bottlenecks.
- **Diagnostics (zPages):** View internal metrics and state. Concept: faster debugging without external tools.
Outcome: A manageable, inspectable observability control plane.
______________________________________________________________________
## Integration with Platform Services
### Application Instrumentation
Services instrumented with OpenTelemetry SDKs automatically emit telemetry:
**Python Services** (API, Agents, Pipelines):
- `opentelemetry-instrumentation-*` libraries for automatic framework instrumentation
- Custom instrumentation for business logic
- OpenInference for AI/ML semantic conventions
**Instrumented Components**:
- FastAPI HTTP requests and responses
- Database operations (MongoDB, PostgreSQL, Redis, Milvus)
- HTTP client requests (httpx, aiohttp, requests)
- LlamaIndex LLM operations
- Python logging framework
### Infrastructure Integration
Non-instrumented services provide telemetry through infrastructure monitoring:
**Container Metrics**: Docker stats receiver collects resource metrics for all containers regardless of instrumentation.
**Health Monitoring**: File log receivers capture health status from both Docker events and synthetic checks.
**Network Observability**: Traefik proxy logs and metrics provide request routing visibility.
______________________________________________________________________
## Multi-Platform Support
### Vendor Flexibility
The OpenTelemetry foundation supports simultaneous export to multiple platforms:
**Supported Platforms**:
- **SigNoz**: Open-source, OpenTelemetry-native platform (current primary)
- **Datadog**: Commercial APM with comprehensive capabilities
- **Grafana Cloud**: Managed Prometheus, Loki, and Tempo
- **New Relic**: Application performance monitoring with AI insights
- **Prometheus**: Open-source time-series database
- **Elasticsearch/ELK**: Log analytics and search platform
- **Splunk**: Enterprise SIEM and observability platform
### Adding Export Destinations
New observability platforms require only collector configuration changes:
1. Define exporter in collector configuration
2. Add exporter to relevant pipelines
3. Configure authentication via environment variables
No application code changes required.
## Security
### Secure Transmission
All telemetry exports use TLS encryption preventing interception or tampering.
### Access Control
Collector configuration and access restricted to infrastructure administrators. Application services emit telemetry
through defined interfaces without collector access.
### Secret Management
Authentication keys managed via environment variables, separate from configuration files, enabling secure secret
rotation.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/13_language_models.md
title: "Language models"
description: ""
---
# Language models
The Swiss AI Hub integrates with language model providers through LiteLLM, a unified gateway that handles routing, cost
tracking, and security. Agents access models through this proxy layer without needing provider-specific code.
## Supported models
LiteLLM supports 100+ LLM providers. The platform can integrate with any provider LiteLLM supports.
The platform uses a dual-mode inference model:
- **Non-GPU deployments**: Swiss LLM Cloud (Swiss-hosted provider) for text generation, embedding, reranking,
transcription, and OCR
- **GPU deployments**: Local vLLM on an NVIDIA RTX 6000 Pro (96 GB VRAM) for fully air-gapped operation
- Any additional OpenAI-compatible API endpoint can be added via LiteLLM configuration
Models are configured in LiteLLM with metadata about capabilities (chat, embedding, vision, function calling), token
limits, and costs. Agents specify which model to use in their configuration. Adding new providers requires updating the
LiteLLM configuration file.
## Architecture
The platform uses three layers:
LLM proxy layer: Provides a unified gateway to language model providers. See [Proxy server](./1_proxy_server/) for
routing, cost tracking, and retry handling.
Agent layer: Agents implement workflows using LLMs through the proxy. See [Guards](./3_guards/) for input and output
validation.
User layer: Users interact with agents through chat interfaces.
## How the layers work together
When a user asks a question:
1. The question reaches the agent
2. Agent input guards (optional) validate the question is appropriate
3. Presidio (if enabled) scans for PII in the question at the proxy layer
4. The proxy routes the request to the configured LLM provider
5. The LLM generates a response
6. Agent output guards (optional) check response quality and redact PII from retrieved documents
7. The response reaches the user
This layered approach provides defense-in-depth for both functionality (guards ensure quality) and security (Presidio
protects user input, output guards protect retrieved data).
## Components
- [Proxy server](./1_proxy_server/): LiteLLM configuration, routing, and cost tracking
- [Data anonymization](./2_anonymization/): Presidio integration for PII protection in user input
- [Guards](./3_guards/): Agent-level input and output validation for quality and security
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/13_language_models/1_proxy_server.md
title: "Proxy server"
description: ""
---
# LLM proxy
The LLM proxy (LiteLLM) provides a centralized gateway to language model providers. It abstracts vendor-specific APIs
behind an OpenAI-compatible interface, allowing the platform to work with multiple AI providers without changing code.
## Configuration
Models are configured in the LiteLLM configuration file. Each model entry specifies the provider, API endpoint,
authentication, and capabilities.
::: details Example model configuration:
```yaml
model_list:
# Cloud model (Swiss LLM Cloud)
- model_name: text-generation/gemma-4-31B-it
litellm_params:
model: openai/google/gemma-4-31B-it
api_base: os.environ/SWISS_LLM_CLOUD_API_BASE_URL
api_key: os.environ/SWISS_LLM_CLOUD_API_KEY
drop_params: true
model_info:
mode: chat
supports_function_calling: true
input_cost_per_token: 0.0000002
output_cost_per_token: 0.0000008
# Local GPU model (vLLM)
- model_name: text-generation/Qwen3-VL-30B-A3B-Instruct-FP8
litellm_params:
model: openai/qwen3-vl-30b
api_base: http://vllm:8000/v1
api_key: os.environ/LOCAL_LLM_TOKEN
drop_params: true
model_info:
mode: chat
supports_function_calling: true
supports_vision: true
input_cost_per_token: 0
output_cost_per_token: 0
```
The `model_name` identifies the model in agent configurations using the real canonical model name. The `litellm_params`
section contains provider-specific connection details. The `model_info` section specifies capabilities and per-token
pricing for cost tracking through Langfuse.
:::
## Core functions
Unified interface: LiteLLM provides an OpenAI-compatible API that works with Swiss LLM Cloud, locally hosted vLLM
models, and other providers. Platform code uses the same interface regardless of which model handles the request.
Request routing: The proxy routes requests based on configured strategy. Current configuration uses "simple-shuffle":
every model group has a single deployment, so a usage-based strategy would only add a Redis write to every call.
Response caching: Only embeddings are cached in Valkey, for 24 hours. Chat completions are not cached, because their
prompts carry whole attached files and are almost never repeated.
Cost tracking: Usage tracking captures token consumption per request. Cost per token is configured for each model,
allowing the platform to calculate and display costs per conversation. See [Cost control](../../14_cost_control/) for
details on cost tracking and optimization.
PII protection: Presidio integration (when enabled) scans requests for personally identifiable information before
sending them to external providers. See [Data Anonymization](../2_anonymization/) for details.
Retry policies: The configuration specifies retry counts for timeout errors, rate limit errors, and internal server
errors.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/13_language_models/2_anonymization.md
title: "Data anonymization"
description: ""
---
# Data anonymization
The Swiss AI Hub integrates Presidio for detecting and anonymizing personally identifiable information (PII) in user
questions before they reach external LLM providers.
## What Presidio protects
Presidio catches PII that users accidentally include in their questions:
- "Can you review this contract for John Smith (john.smith@company.com)?"
- "What's the policy for credit card 4532-1234-5678-9012?"
- "How do I process the invoice for phone number +41 79 123 45 67?"
Without Presidio, this PII gets sent to external LLM providers (OpenAI, Google) as part of the API request.
## How it works
Presidio runs as a guardrail in the LiteLLM proxy layer. It scans user questions for PII patterns before sending
requests to language models.
Two anonymization modes:
Mask mode replaces detected PII with placeholders like `[PERSON]` or `[EMAIL_ADDRESS]`. The question "Can you review
this contract for John Smith?" becomes "Can you review this contract for [PERSON]?" before reaching the LLM. The model
can still understand context and generate a useful response.
Block mode rejects the entire request when certain PII types appear. Use this for highly sensitive data like credit card
numbers. The user receives an error message instead of a response.
## Supported PII types
Presidio detects person names, email addresses, credit card numbers, phone numbers, social security numbers, IP
addresses, and dates or locations that could identify individuals. Detection uses pattern matching, regular expressions,
and named entity recognition models. The system supports multiple languages including English, German, French, and
Italian.
## Configuration
Presidio guardrails are configured in LiteLLM but disabled by default. Administrators enable them per deployment based
on data sensitivity requirements.
::: details Example guardrail configuration:
```yaml
guardrails:
- guardrail_name: "presidio-mask-guard"
litellm_params:
guardrail: presidio
default_on: false
mode: "pre_call"
presidio_language: "de"
output_parse_pii: true
pii_entities_config:
PERSON: "MASK"
EMAIL_ADDRESS: "MASK"
- guardrail_name: "presidio-block-guard"
litellm_params:
guardrail: presidio
default_on: false
mode: "pre_call"
pii_entities_config:
CREDIT_CARD: "BLOCK"
```
Configure which PII types to mask or block through the `pii_entities_config` section. Set `default_on: true` to enable
the guardrail for all requests.
:::
## When to use anonymization
| Scenario | Recommendation |
| --------------------------------------------- | -------------------------------------------------------------------- |
| External LLM providers (OpenAI, Google, etc.) | Enable Presidio to protect data before it leaves your infrastructure |
| Self-hosted models on-premises | Optional - your data never leaves your control |
| User-generated content | Enable if users might accidentally include PII |
| Pre-sanitized data | Skip Presidio to avoid unnecessary overhead |
| Regulatory requirements (GDPR, HIPAA) | Enable to demonstrate PII protection controls |
Presidio applies to all requests when enabled. Use block mode sparingly - only for highly sensitive PII types like
credit cards where you need hard rejection rather than masking.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/13_language_models/3_guards.md
title: "Input/output guards"
description: ""
---
# LLM guards
Guards check AI agent interactions in real time. They catch inappropriate questions before the agent sees them and
screen responses before users receive them. Unlike evaluations that test agents before deployment, guards run during
live conversations.
## How guards work
Guards check conversations at two points:
Input guards analyze user questions before the agent processes them. They filter out off-topic requests, block policy
violations, or ask for clarification.
Output guards examine agent responses before delivery. They verify quality, redact sensitive information, and catch
hallucinations or harmful content.
::: tip Complete PII protection
Guards work at the agent level. For platform-level PII protection that catches sensitive information in user inputs
before they reach any agent, see [Data Anonymization](../2_anonymization/) which covers Presidio integration. Use both
layers for defense-in-depth.
:::
## Available guards
The Swiss AI Hub includes several guards that address specific risks. Which guards you can enable depends on how your
agent was built.
### Input guards
Agent description guard: Checks that questions match what the agent does. A financial compliance agent would block
"What's the weather?" and explain it only handles financial questions.
Few-shot guard: Enforces custom policies through examples. If your company prohibits using work assistants for
entertainment, you'd provide examples like "Recommend a movie" (blocked) and "Recommend a project management tool"
(allowed). The guard learns to recognize similar patterns.
### Output guards
Context sufficient guard: Checks whether the agent has enough information to answer accurately. Particularly useful for
RAG agents that pull from knowledge bases. If a user asks a detailed technical question but the retrieved documents
don't contain enough detail, the guard stops the response and tells the user the information isn't available.
::: tip Configuration note
Some agents (like the RAG Agent) can use the context sufficient guard automatically to prevent responses without
adequate evidence.
:::
Sensitive info guard: Detects and redacts confidential or personally identifiable information in agent responses. This
catches PII that appears in retrieved documents. For example, if an agent pulls a document containing an employee email
address, the guard redacts it before the user sees it, replacing it with `[REDACTED]`.
## When to use guards
| Agent type | Recommended guards |
| -------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Customer-facing agents | Agent description guard, Few-shot guard (for policies), Context sufficient guard |
| Compliance-critical domains (healthcare, finance, legal) | All guards + [Presidio PII protection](../2_anonymization/) |
| Internal knowledge assistants | Agent description guard, Context sufficient guard |
| Narrow-scope specialized agents | Context sufficient guard (minimal guardrails needed) |
| Development/testing environments | Optional (prioritize speed over safety) |
## Relationship with Presidio
Guards and [Presidio anonymization](../2_anonymization/) operate at different layers to provide complete PII protection:
| Layer | Component | Purpose |
| ------------------------------ | ------------------------------------ | ------------------------------------------------------------------------ |
| LiteLLM proxy (platform-level) | Presidio | Removes PII from user questions before they reach external LLM providers |
| Agent (application-level) | Input guards | Validate question appropriateness and scope |
| Agent (application-level) | Output guards (Sensitive info guard) | Detect PII in responses from retrieved documents |
Presidio protects **user input** from being sent to external providers. The sensitive info guard protects **agent
responses** that might contain PII from your knowledge base documents. Both are needed for complete PII protection.
## Configuration
Guards get built into agents during development. How much control you have depends on the agent's design. Some agents
ship with mandatory guards you can't disable. Others let you toggle specific guards through the configuration interface.
Some don't support customization at all.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/14_cost_control.md
title: "Cost control"
description: ""
---
# Cost control
AI agents cost money to operate. The Swiss AI Hub tracks these costs so you can optimize spending, justify investments,
and forecast budgets.
## How AI costs work
AI providers charge based on token usage. Tokens are small chunks of text (roughly 4 characters) that models process.
Cost model comparison:
| Model | Type | Cost Structure |
| ------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| API (Pay-per-token) | Variable OPEX | Pay providers (OpenAI, Google) for each token processed |
| Locally Hosted | CAPEX + Fixed OPEX | Capital expense for hardware (GPUs, servers) plus ongoing costs for power and MLOps staff. Per-token cost is \$0, but fixed infrastructure cost is high |
Each interaction consumes different types of tokens at different price points:
::: details Prompt tokens
Your input to the AI, including questions, conversation history, system prompts, and retrieved documents. Longer prompts
cost more.
:::
::: details Completion tokens
The AI-generated responses. Longer, more detailed responses cost more.
:::
::: details Embedding tokens
Document processing for search and retrieval. Typically cheaper than text generation.
:::
::: details Model tiers
| Tier | Examples | Use Case | Cost |
| --------- | ---------- | ---------------------------------------- | ------- |
| Flagship | GPT-5 | Complex reasoning, high-accuracy tasks | Highest |
| Balanced | GPT-5 mini | Standard workflows, internal assistants | Medium |
| Efficient | GPT-5 nano | High-volume simple tasks, classification | Lowest |
:::
## Cost tracking
The Swiss AI Hub tracks costs for each conversation. When you chat with an agent, the platform records token usage and
calculates the cost. This information appears in the conversation thread.
Tracking works for all AI models, whether you use cloud services like OpenAI or self-hosted models. For self-hosted
models, you can assign a cost value to track spending consistently.
You can view cost information per conversation to see which questions are most expensive. This helps with agent design
decisions, model selection, and budget planning.
## Budgets and rate limits
LiteLLM provides per-user budget and rate limiting capabilities through its user management system. These controls are
configured through environment variables and enforced automatically by the proxy.
Available controls:
- Max budget: Hard limit on spending per user within a budget period. Blocks requests when exceeded.
- Soft budget: Alert threshold that triggers notifications without blocking requests.
- Budget duration: Time period for budget reset (e.g., "30d" for monthly budgets). Without this, budgets never reset.
- TPM limit: Maximum tokens per minute a user can consume.
- RPM limit: Maximum requests per minute a user can make.
- Max parallel requests: Maximum concurrent requests a user can have in flight.
::: details Configuration via environment variables
```bash
LITE_LLM_PROXY_USER_MAX_BUDGET=100.0 # $100 hard limit
LITE_LLM_PROXY_USER_SOFT_BUDGET=80.0 # Alert at $80
LITE_LLM_PROXY_USER_BUDGET_DURATION="30d" # Reset monthly
LITE_LLM_PROXY_USER_TPM_LIMIT=10000 # 10k tokens/minute
LITE_LLM_PROXY_USER_RPM_LIMIT=60 # 60 requests/minute
LITE_LLM_PROXY_USER_MAX_PARALLEL_REQUESTS=5 # 5 concurrent requests
```
These settings apply to new users created in the system. Existing users retain their configured limits.
:::
::: warning Not currently enabled
While the infrastructure supports these limits, they are not enabled by default. Set the environment variables above to
activate budget and rate limiting.
:::
## Optimization strategies
### Model selection
Match the model tier to your task. Use flagship models (GPT-5) for complex, customer-facing, or high-accuracy tasks. Use
balanced models (GPT-5 mini) for internal assistants or standard workflows. Use efficient models (GPT-5 nano) for
classification, data extraction, or high-frequency chat.
### Locally hosted models
Local hosting shifts spending from variable per-token fees to fixed infrastructure costs. Organizations choose this for
data privacy (HIPAA, GDPR), compliance, and IP protection, not for immediate cost savings. It requires capital
investment (GPUs, servers) and ongoing operational costs (power, MLOps staff).
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/15_memory.md
title: "Memory"
description: ""
---
# Agent memory
AI agents need context to provide relevant, personalized assistance. Without memory, agents start every conversation
fresh, forcing you to repeatedly explain your preferences, working style, and organizational context. The Swiss AI Hub's
memory system enables agents to learn and retain information across conversations.
## Why memory matters
Memory enables capabilities that stateless agents can't provide. An agent learns that you prefer concise code examples
in Python, then automatically adjusts future responses to match your working style. When one user corrects an agent
about a company policy, that correction benefits everyone – all agents immediately work from the updated information.
You explain your project structure once, and agents remember it for all future interactions. Information survives
employee turnover; when team members leave, their documented knowledge remains accessible to agents serving new
employees.
## Two types of memory
The platform uses two distinct memory types:
**User memory** stores personal preferences and individual context. This memory is private to you. When an agent learns
that you prefer detailed explanations or work primarily in a specific technology stack, that information remains yours
alone.
**Organization memory** stores shared knowledge accessible to all users within your organization. Company policies,
project details, team conventions, and factual information belong here. When one user documents that "Project Falcon
uses microservices architecture," all agents can leverage that knowledge to assist any team member working on that
project.
This structure balances individual personalization with organizational consistency. Agents adapt to each user's
preferences while maintaining a common understanding of company-wide facts.
## How memory works
### Automatic learning
User memory requires no manual effort. Agents automatically extract relevant information from your conversations. As you
chat with a code assistant about Python projects, it learns your language preference. As you discuss deadlines with a
process assistant, it learns your approval patterns.
Each agent type learns differently. A code assistant focuses on technical preferences and working styles. A knowledge
retrieval agent learns about your areas of interest and preferred information formats.
### Explicit documentation
Organization memory works differently. Because this knowledge affects all users, the platform requires explicit
documentation rather than automatic inference. You choose what information to preserve as organizational knowledge.
This design prevents errors from propagating across the organization. An agent won't accidentally turn a one-time
comment into permanent company policy.
### Recall in a conversation
For every message, a chat agent looks up the memories that bear on it, user and organization memory alike, and gives
them to the model together with the other context it gathered (attached files, referenced knowledge) as part of its
system instructions. The event history shows exactly which memories were recalled. An agent that lets the model choose
its own tools can also offer memory as a tool, so it recalls only when the question calls for it. After the answer is
delivered, new facts are stored in the background, so storing never delays the reply.
### Transparency
The platform maintains full visibility into what agents remember. Every memory includes the conversation thread where it
originated, the agent that created it, and the exact timestamp. When an agent uses a memory to inform its response, that
usage is logged and visible in the conversation trace. Through the Swiss AI Agent Protocol, all memory operations
integrate with the platform's observability system.
You can see exactly what agents remember about you, where that information came from, and when it's being used.
## Your control over memory
### Managing user memory
You have complete control over your personal memories. Access the User Memory service to see everything agents have
learned about you. You can edit any memory to correct or refine what agents remember, delete specific memories, or
delete all user memories entirely.
The platform respects data sovereignty and GDPR requirements. You can exercise your right to be forgotten by deleting
all user memories with a single action.
### Managing organization memory
Organization memory follows a similar pattern with one key difference: changes affect all users. When you edit or delete
an organization memory, you're modifying knowledge that other team members' agents rely on.
Access to organization memory management is typically restricted to administrators or knowledge managers who understand
the impact of their changes.
## Getting started
Navigate to the **User Memory** service in the platform interface. You'll see all the information agents have learned
about your preferences and working style. Each memory shows the remembered information, when it was learned, which agent
created it, and the conversation thread where it originated. Try editing a memory to see how agents adapt to your
corrections.
Access the **Organization Memory** service to view your organization's shared knowledge – company policies, project
information, and team conventions that all agents use. If you have appropriate permissions, you can contribute new
organizational knowledge or update existing information.
During conversations with agents, observe the display events to see when agents retrieve and use memories. This
transparency shows you exactly how memory influences agent responses.
## Privacy and data sovereignty
The memory system adheres to Swiss data sovereignty principles. Your personal memories are never visible to other users.
Organization memories are scoped to your tenant, preventing information leakage between different organizations using
the platform. Large organizations can further isolate organization memories by department or team.
Both memory types support full CRUD operations (create, read, update, delete), ensuring compliance with data protection
regulations. All memory data remains on Swiss-based infrastructure with the same data protection guarantees as the rest
of the platform.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/15_memory/1_user_memory.md
title: "User Memory"
description: ""
---
# User memory
User memory enables AI agents to learn your personal preferences, working style, and individual context. This memory is
private to you - no other user can see what agents have learned about your preferences. Over time, agents adapt their
behavior to match how you work.
## What gets remembered
Agents automatically learn information that helps them serve you better. This includes your preferred programming
languages, communication style, level of detail in responses, and format preferences. If you consistently ask for
concise answers, agents learn to be brief. If you prefer detailed explanations with examples, they adapt accordingly.
Agents also learn your technology stack, current projects, typical tasks, and areas of expertise. When you frequently
discuss Python development, agents recognize this and frame technical suggestions in Python terms. Process patterns like
how you prefer to handle approvals, your typical working hours, and workflow habits get captured too.
User memories are strictly isolated to your account. When an agent learns that you prefer detailed code comments, that
preference affects only your interactions. Other users might prefer minimal comments, and their agents adapt
differently. This isolation extends across agent types - each agent learns preferences relevant to its function without
interfering with others.
## How it works
User memory requires no manual effort. As you converse with agents, they analyze the conversation to identify useful
information worth remembering. After processing your conversation, the agent asks itself: "What did I learn about this
user's preferences or context that would help me serve them better in the future?"
This approach means you don't maintain preference lists or fill out profile forms. Agents learn from how you actually
work, not how you think you work. As your preferences evolve, new memories reflect those changes.
Different agent types focus on different aspects. Code assistants learn your preferred languages, frameworks, coding
style, and the types of examples you find helpful. Knowledge retrieval agents learn which topics interest you, your
preferred information depth, and how you like sources cited. Process orchestration agents learn your approval
thresholds, notification preferences, and how you interact with automated workflows.
Agents maintain awareness of when memories were created. If you mentioned working on "the new dashboard project" six
months ago and mention it again today, the agent understands these might be different projects or different phases of
the same project.
## Viewing your memories
Access the **User Memory** service through the platform's navigation to see everything agents have learned about you.
Each memory displays the actual information remembered (e.g., "User prefers Python over JavaScript for backend
development"), when this memory was learned, which agent identified this preference, and the thread where this
information originated. You can click through to the source conversation to see the exact context that generated the
memory.
The interface supports semantic search. Instead of exact keyword matching, you can search by meaning. Searching for
"programming languages" will find memories about your Python preference, JavaScript experience, and TypeScript
interests, even if those exact words don't appear in your search.
Some memories connect to others through relationships. If agents learn you "work on Project Falcon" and "Project Falcon
uses microservices," the interface can show these connections as a graph, helping you understand how agents connect
different pieces of information about your context.
## Managing your memories
Click any memory to edit its content. This is useful when an agent learned something slightly incorrect, your
preferences have changed, or you want to refine how something is expressed. Editing a memory immediately affects how
agents use that information - if you correct "User prefers brief responses" to "User prefers detailed explanations with
examples," agents adapt their behavior in the next conversation.
You can delete individual memories that are no longer relevant or accurate, or delete all your user memories at once.
Common reasons include role changes that make old technical preferences irrelevant, completed projects whose context no
longer applies, privacy concerns about specific information, or wanting agents to relearn your preferences from scratch.
Deletion is immediate and irreversible.
::: info GDPR compliance
The platform fully supports your data protection rights: access (view all memories), rectification (edit any memory),
erasure (delete specific or all memories), and data portability (export via API). Deleting all user memories satisfies
your right to be forgotten regarding preference data.
:::
## Practical examples
### Code assistant adaptation
In week one, you ask a code assistant for help with a Python function. The agent creates a memory: "User works with
Python." The following week, you ask about authentication. Without being told, the agent provides Python examples rather
than other languages. It also notices you appreciate inline comments and creates another memory about your documentation
preferences. By week four, when you ask about a complex algorithm, the agent automatically provides Python code with
detailed comments, having learned both preferences from your actual behavior.
### Knowledge retrieval personalization
You ask a knowledge agent about machine learning and receive a comprehensive overview. The agent notes your interest.
When you ask for more details on a specific aspect, it provides technical depth and remembers that you prefer detailed
technical explanations over high-level summaries. In later interactions about different topics, the agent automatically
provides similar depth and proactively mentions connections to machine learning.
### Process agent learning
A process agent asks you to approve a document. You approve with comments about formatting, and the agent learns you
check formatting before approval. In subsequent requests, it includes formatting verification in its pre-approval
checklist. Over time, it also learns when you prefer to receive approval requests based on your response patterns.
## Memory evolution
User memories are not static. Early memories might be broad: "User works with web development." Later memories refine
this: "User specializes in frontend development using Vue.js and TypeScript." Agents don't delete the earlier memory -
both remain. The more specific memory takes precedence when relevant, while the broader memory applies in general
contexts.
If you explicitly contradict an earlier preference, agents can recognize this. If they learned "User prefers brief
responses" but you ask for more detail, they'll create a new memory reflecting the change. The platform preserves both
memories with timestamps, allowing agents to understand your preferences have evolved rather than treating the
contradiction as an error.
Agents develop stronger confidence in preferences they observe repeatedly. A one-time interaction creates a tentative
memory; consistent behavior reinforces that memory's importance.
## Best practices
Don't try to "program" agents by making artificial statements about your preferences. Work naturally, and agents will
learn from your actual behavior. This produces more accurate memories than trying to dictate preferences upfront.
Check your user memories occasionally to see what agents have learned. This helps you catch incorrect inferences early,
understand why agents behave certain ways, and decide if you want to adjust any learned preferences. If an agent learned
something incorrect, edit the memory directly rather than trying to correct it through conversation - direct editing is
faster and more reliable.
User memory is designed to work in the background. You don't need to think about it constantly. The transparency
features exist for verification and control, not because you need to actively manage the system.
::: details How memory affects performance
User memories are retrieved for each message you send, using the question the agent derived from the conversation. The
retrieval adds minimal latency – typically under 100ms. Semantic search ensures only relevant memories are included in
the agent's context.
:::
::: details Memory limits
The system doesn't impose hard limits on user memory count. Agents focus on the most relevant memories for each
conversation. Having thousands of memories doesn't slow down interactions because only pertinent information is
retrieved.
:::
::: details Cross-agent memory sharing
While each agent type specializes in what it learns, factual memories (not preferences) can be shared across agents. If
one agent learns "User is working on Project Falcon," other agents can access that factual context. Preferences like
"User prefers concise responses" remain agent-specific.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/15_memory/2_organization_memory.md
title: "Organization Memory"
description: ""
---
# Organization memory
Organization memory enables AI agents to share knowledge across your entire organization. When one user documents a
company policy, project detail, or team convention, that information becomes available to all agents serving all users.
This shared knowledge creates consistency and builds an institutional knowledge base that survives employee turnover.
Unlike user memory, which adapts agents to individual preferences, organization memory ensures agents work from the same
factual foundation when assisting different team members.
## Why shared memory matters
When you correct an agent's mistake about company policy, that correction benefits everyone. The agent doesn't just
learn for your conversations – it improves for all users. Without shared memory, every user would need to make the same
correction independently. With organization memory, one correction updates the shared knowledge base and all agents
immediately operate with accurate information.
Different users asking about the same project receive consistent information. Agents don't provide contradictory answers
about company policies, project architectures, or team procedures. This consistency helps with onboarding (new employees
interact with agents that already understand company conventions), cross-team collaboration (agents helping different
departments work from the same facts), and policy enforcement (agents apply policies uniformly regardless of who they're
assisting).
When team members leave, their documented knowledge remains. An experienced developer's insights about architectural
decisions or system limitations survive as organization memories.
## What gets remembered
Organization memory stores factual information relevant to multiple users: company policies (deployment schedules,
approval workflows, security requirements, coding standards), project information (architecture decisions, technology
choices, team assignments), team conventions (naming conventions, file organization patterns, communication protocols),
and technical facts ("Our API uses OAuth2 authentication," "Production database is PostgreSQL 15," "We use semantic
versioning for all releases").
Organization memory focuses on objective facts that apply to everyone, not subjective preferences that vary by
individual.
| Type | Example | Memory type |
| -------------------- | ------------------------------------------------------- | ------------ |
| Shared fact | "We deploy to production on Fridays" | Organization |
| Personal preference | "User prefers deployment notifications the day before" | User |
| Project architecture | "Project Falcon uses microservices" | Organization |
| Individual focus | "User works primarily on Project Falcon's auth service" | User |
## Visibility and access
By default, organization memories are visible to all users within your tenant. This visibility is intentional –
organization memory serves as common ground for all agents regardless of which user they're assisting.
Large organizations can scope organization memories to specific departments or teams through namespaces. An engineering
namespace might contain technical architecture and deployment procedures, while an HR namespace contains benefits
information and onboarding procedures. Department scoping prevents information overload; an engineering agent doesn't
need to process HR policy memories when helping with code reviews.
::: info Namespace configuration
Namespace scoping is configured at the tenant level by administrators. Most organizations use a single namespace or a
small number of department-level namespaces.
:::
While viewing organization memories is typically open to all tenant users, creating, editing, and deleting organization
memories often requires elevated permissions. This prevents accidental modifications to shared knowledge. Organizations
typically grant these permissions to knowledge managers, team leads, or system administrators.
## Creating organization memories
Unlike user memory, which agents infer automatically from conversations, organization memory requires explicit
documentation. You must deliberately choose to preserve information as organizational knowledge. Because organization
memories affect all users, the platform ensures you're aware you're creating shared knowledge rather than personal
notes.
Create organization memories when you've corrected an agent (so other users don't encounter the same error), when you
find yourself explaining the same context repeatedly, when new employees would need this information, or when the
information is objectively true and relevant to multiple users.
Access the **Organization Memory** service through the platform interface. Create a new memory by providing the factual
information, enough context for agents to know when it applies, and sources if applicable (policy documents,
architecture decisions, official announcements). The platform automatically tracks who created the memory and when.
::: tip Writing effective memories
Be specific: "We deploy to production every Friday at 17:00 CET" beats "We have a regular deployment schedule." State
current facts in present tense: "Project Falcon uses microservices" rather than "We decided to use microservices." Keep
memories focused – one fact per memory. When facts change, update the existing memory rather than creating a new one.
:::
## Viewing and managing
Navigate to the **Organization Memory** service to view all shared knowledge. The interface shows a searchable list of
documented organizational knowledge, creation details (who documented each memory and when), usage tracking (when agents
retrieve specific memories), and relationships between related pieces of information.
Use semantic search to find relevant information by meaning. Searching for "authentication" will find memories about
OAuth2, SSO, API keys, and identity providers, even if those exact terms don't appear in your search.
Organization memories connect through relationships. "Project Falcon" relates to "microservices architecture" which
relates to "NATS messaging." The platform visualizes these as a graph, helping you understand context, identify
documentation gaps, and verify consistency.
If you have appropriate permissions, you can edit organization memories to correct inaccuracies, update changed facts,
or add context. Editing affects all users immediately. Deleting requires careful consideration – delete only when
information is no longer true, was created in error, or is fundamentally incorrect.
## How corrections propagate
```mermaid
sequenceDiagram
participant UserA as User A
participant Agent as Agent
participant OrgMem as Organization Memory
participant UserB as User B
UserA->>Agent: "What CI/CD do we use?"
Agent->>UserA: "Jenkins" (incorrect)
UserA->>Agent: "Actually, we use GitHub Actions"
Agent->>OrgMem: Creates memory
Note over OrgMem: "We use GitHub Actions for CI/CD"
UserB->>Agent: "What CI/CD do we use?"
Agent->>OrgMem: Retrieves memory
Agent->>UserB: "GitHub Actions"
```
One user's correction improves the platform for everyone. When an administrator updates a memory (for example, changing
deployment schedule from Fridays to Thursdays), all agents instantly operate with the new information without retraining
or configuration updates.
## Practical examples
### Technical documentation
Your infrastructure team uses a specific authentication pattern. You create a memory: "Our backend services authenticate
using OAuth2 tokens issued by the central identity service at auth.company.com. All API requests must include the token
in the Authorization header as a Bearer token."
A code assistant helping a developer build a new service automatically includes proper authentication code. A
documentation agent describes the correct pattern without being told. An onboarding agent explains the authentication
system accurately to new developers.
### Policy enforcement
Your organization has a security policy about data handling. You create a memory: "Personally identifiable information
(PII) must never be logged in application logs. Use data masking for any debugging scenarios that involve PII."
Code assistants proactively check for PII logging in code reviews. Debugging agents suggest data masking when developers
need to troubleshoot PII-related issues.
### Cross-team collaboration
Different teams document their handoff requirements. Marketing creates: "Product launch campaigns require 2-week notice
to the engineering team for feature flag preparation." Engineering creates: "Feature flags are managed through
LaunchDarkly and require product owner approval before deployment."
A marketing agent helping plan a campaign automatically accounts for the 2-week engineering notice. An engineering agent
helping with feature flags knows to involve product owners.
## Responsibility and accuracy
Incorrect organization memories affect everyone. If you document "We deploy on Fridays" when the actual schedule is
Thursdays, every agent will provide wrong information to every user. Before creating or editing organization memories,
verify the information is accurate – consult authoritative sources, check with relevant teams, or confirm with
administrators.
Unlike configuration changes that might require deployment, organization memory updates affect agents instantly.
Consider the impact before making changes. If you're uncertain, consult with colleagues or administrators.
Organization memory works best when treated as a collaborative asset. Subject matter experts document their domain
knowledge, team leads capture procedures, experienced employees preserve institutional knowledge, and new employees ask
questions that reveal documentation gaps.
## Governance
Periodically review organization memories to identify outdated information, consolidate redundant memories, fill
documentation gaps, and improve clarity. Many organizations establish a quarterly review cycle.
The platform tracks all changes to organization memories – who created each memory, who made edits and when, and what
changed. This audit trail supports compliance requirements and helps resolve questions about when information was
updated.
Consider whether to delete or update memories about completed projects or deprecated systems. Some organizations prefer
updating memories to state "Project Falcon was completed in Q2 2024 and is no longer in active development" rather than
deleting them, preserving historical context while clarifying current state.
## Getting started
Start by exploring what organization memories already exist. This helps you understand what agents already know,
identify gaps where documentation would be valuable, and learn what your colleagues have documented.
The easiest time to create organization memory is when you correct an agent. If an agent states incorrect information,
document the correction so other users don't encounter the same error.
If you're a subject matter expert, document key facts about your area. You don't need comprehensive guides – simple
factual statements work well. "The customer database is PostgreSQL 15" is valuable even without extensive detail.
::: details Memory retrieval mechanism
Agents retrieve organization memories using semantic search (finding relevant context by meaning) and graph traversal
(following relationships between concepts). This dual approach ensures agents find both directly relevant information
and related context.
:::
::: details Multi-tenancy and isolation
Organization memories are strictly isolated by tenant. If your platform hosts multiple organizations, each tenant's
memories are completely separate.
:::
::: details Performance considerations
The number of organization memories doesn't significantly affect agent performance. Agents retrieve only relevant
memories through semantic filtering. Organizations with thousands of memories experience the same response times as
those with dozens.
:::
::: details Integration with user memory
Agents use both memory types together. They might retrieve organization memory about "Project Falcon's architecture"
while also considering user memory about "User specializes in Project Falcon's authentication service" to provide
contextually relevant assistance.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/16_multi_tenancy.md
title: "Multi-tenancy"
description: ""
---
# Multi-tenancy
Multi-tenancy lets you create organizational boundaries within a single platform instance. Each tenant represents a
workspace with its own users, roles, and access to agents and services.
The platform's multi-tenant model gives you flexibility to structure access in ways that match your organization's
needs, from simple departmental separation to complex hierarchical configurations.
## Three resource types
The platform provides three types of resources:
**Services** are platform capabilities like user management, knowledge base management, agent evaluation, and system
configuration. Most users don't need direct service access.
**Agents** are AI assistants that perform specific tasks. Users interact with agents through chat or agents execute
tasks autonomously. Agents access data and perform actions based on how they're configured.
**Processes** orchestrate workflows involving multiple agents, human approvals, and external systems. Processes
coordinate complex business operations.
## The agent independence principle
::: info The Most Important Concept
**Agents operate independently of user and tenant permissions.**
When a user chats with an agent, the agent doesn't inherit that user's access restrictions. The agent accesses data
based on its own configuration. This has profound implications for how you structure tenants and control data access.
:::
Why design it this way? Agents often need to:
- Access data from multiple sources that individual users can't see
- Run autonomously without any user interaction
- Participate in group conversations with users who have different permissions
- Execute scheduled tasks regardless of who's online
You control data access by controlling which users can interact with which agents. The agent's configuration determines
what data it can access. Your tenant design determines who can use that agent.
## Common patterns
Most organizations use a three-tier structure:
**System administrator tier**: A tenant for platform administrators who develop agents, deploy pipelines, and maintain
the infrastructure. These users see everything and can modify system-level configurations.
**Management tier**: A tenant for people who administer the platform for business users. They create tenants, assign
users to tenants, configure agent instances, and monitor usage. They can't deploy new agent code or modify
infrastructure.
**Department tiers**: One tenant per department, business unit, or customer. Users work with agents relevant to their
department. They can't see agents for other departments or access administrative functions.
This structure separates technical operations, business administration, and day-to-day use.
## Flexibility and responsibility
The system is intentionally flexible. You can create permissive configurations where agents access all data, or
restrictive setups where agents have narrowly scoped access to specific information.
This flexibility means you're responsible for:
- Designing a tenant structure that matches your security requirements
- Configuring agents with appropriate data access
- Assigning users to the right tenants with appropriate roles
- Regularly reviewing who can access which agents
The platform enforces the boundaries you define. It won't stop you from creating an agent that accesses everything and
giving that agent to everyone. That's a valid configuration if it matches your needs - but you must understand the
implications.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/16_multi_tenancy/1_concept.md
title: "How multi-tenancy works"
description: ""
---
# How multi-tenancy works
Understanding multi-tenancy requires understanding how the platform separates people, agents, and data. This separation
creates both powerful flexibility and important responsibilities.
## A practical example
Let's walk through a realistic deployment to see how the pieces fit together.
### The scenario
Your company has five departments (Legal, Finance, HR, Operations, Sales). You want everyone to access information about
the company's vision and identity, but each department should only see their own internal guidelines.
### Step 1: System administrators prepare the data
The platform's system administrators (sysadmins) set up data pipelines:
- One pipeline ingests the company vision, mission, and identity documents into a knowledge database called
`company-vision`
- Five separate pipelines process each department's internal guidelines into their own databases: `legal-guidelines`,
`finance-guidelines`, `hr-guidelines`, `operations-guidelines`, `sales-guidelines`
You now have six knowledge databases containing parsed, searchable documents.
### Step 2: Sysadmins deploy the agent class
The sysadmins deploy a RAG (Retrieval-Augmented Generation) agent class to the platform. This agent class is
configurable - you can give each instance a custom system prompt and specify which knowledge database it should search.
The agent class itself doesn't contain any data or know which database to use. It's a template waiting to be configured.
### Step 3: Managers create agent instances
People in the management tenant can't deploy agent code, but they can create instances from deployed classes. They
create six agent instances:
- **Company Vision Agent**: Configured to search only `company-vision`
- **Legal Guidelines Agent**: Configured to search only `legal-guidelines`
- **Finance Guidelines Agent**: Configured to search only `finance-guidelines`
- **HR Guidelines Agent**: Configured to search only `hr-guidelines`
- **Operations Guidelines Agent**: Configured to search only `operations-guidelines`
- **Sales Guidelines Agent**: Configured to search only `sales-guidelines`
Each agent instance has its data access baked into its configuration. The Company Vision Agent can only search company
vision documents. The Finance Guidelines Agent can only search finance guidelines. This isn't determined by who's
chatting with the agent - it's configured when the instance is created.
### Step 4: Managers configure tenant access
The managers have already created five department tenants. Now they configure which agents each tenant can see:
- Legal tenant: Can use Company Vision Agent and Legal Guidelines Agent
- Finance tenant: Can use Company Vision Agent and Finance Guidelines Agent
- HR tenant: Can use Company Vision Agent and HR Guidelines Agent
- Operations tenant: Can use Company Vision Agent and Operations Guidelines Agent
- Sales tenant: Can use Company Vision Agent and Sales Guidelines Agent
All tenants can use the Company Vision Agent (company-wide information). Each tenant can only use their own guidelines
agent.
### What this creates
A user in the Finance tenant sees two agents in their interface: Company Vision Agent and Finance Guidelines Agent. They
can chat with either one. They cannot see that the Legal Guidelines Agent exists.
The Finance Guidelines Agent will only answer questions from the finance guidelines database. Even though the user in
Finance tenant can use this agent, they still can't access HR or Legal guidelines - because the agent itself can't
access those databases.
```mermaid
graph LR
subgraph Data Layer
CV[company-vision DB]
FG[finance-guidelines DB]
LG[legal-guidelines DB]
end
subgraph Agent Layer
CVA[Company Vision Agent]
FGA[Finance Guidelines Agent]
LGA[Legal Guidelines Agent]
end
subgraph Tenant Layer
FT[Finance Tenant]
LT[Legal Tenant]
end
CV --> CVA
FG --> FGA
LG --> LGA
CVA --> FT
FGA --> FT
CVA --> LT
LGA --> LT
style CV fill:#e8f5e9
style FG fill:#fff3e0
style LG fill:#fff3e0
style CVA fill:#e1f5ff
style FGA fill:#e1f5ff
style LGA fill:#e1f5ff
style FT fill:#f3e5f5
style LT fill:#f3e5f5
```
## The counterintuitive part
::: warning Users Don't Access Data Directly
**Users in department tenants cannot access the knowledge service at all.**
The Finance tenant doesn't have permission to view knowledge databases, browse documents, or see ingestion status. Yet
users in Finance tenant can chat with agents that access knowledge databases.
This seems contradictory but it's intentional.
:::
Regular users don't benefit from seeing raw parsed documents and database metadata. That's administrative information.
Users benefit from chatting with agents that answer questions using that data.
The managers in the management tenant do have access to the knowledge service. They can see all six databases, verify
documents were ingested correctly, and troubleshoot issues. But they're administrators, not end users.
## Why agents don't inherit permissions
Let's explore why the Finance Guidelines Agent can access the `finance-guidelines` database even when the user chatting
with it can't.
**Agents run autonomously**. An agent might:
- Execute on a schedule with no user involved
- Participate in a Slack channel with multiple users who have different permissions
- Be triggered by a process that runs in the background
- Need to access data from multiple sources to synthesize an answer
If agents inherited user permissions, you couldn't do any of this reliably. The agent's behavior would change based on
who triggered it, making it impossible to test or predict.
**Agents are configured for specific purposes**. When you create the Finance Guidelines Agent, you're saying "this agent
exists to answer questions about finance guidelines." Its entire purpose requires accessing that specific database. The
access is part of its identity.
**You control access by controlling agent access**. Want to give someone access to finance guidelines? Give them access
to the Finance Guidelines Agent. Want to revoke it? Remove their access to that agent. The data access follows the agent
design, not the user permissions.
## Benefits of this model
This separation between agent capabilities and user permissions creates powerful possibilities:
**Synthesize information across boundaries**. Create an agent that accesses multiple databases users can't see
individually. A C-level Executive Agent might search across all department guidelines to answer strategic questions.
Only executives get access to this agent.
**Predictable agent behavior**. The Finance Guidelines Agent behaves identically whether the CFO or an intern chats with
it. This makes testing meaningful and reduces security concerns about variable behavior.
**Granular access control**. Instead of managing who can access which documents, you manage who can access which agents.
This aligns with how people actually work - they need to accomplish tasks, not browse document repositories.
**Clear audit trails**. When someone asks the Finance Guidelines Agent a question, you know exactly which data was
accessed (finance guidelines only). You don't need to check user permissions to understand what happened.
## Responsibilities of this model
::: details Configuration Responsibilities
This flexibility requires thoughtful configuration:
**Don't create overly broad agents**. You could create one RAG agent with access to all six databases and give it to
everyone. But now you can't control who sees what - everyone can ask about everything. The agent becomes a security
bypass. Instead, create focused agents with narrow data access and give those agents to appropriate users.
**Understand the trust model**. When you give someone access to an agent, you're trusting them with whatever that agent
can do. The Finance Guidelines Agent can read all finance guidelines. If you give someone access to that agent, they can
ask it about any finance guideline. The agent won't respect row-level or field-level permissions - it's configured to
access the entire `finance-guidelines` database.
**Test agent data access**. Create test instances of agents in isolated tenants. Verify they can only access intended
data. Don't assume an agent respects user permissions - it doesn't.
**Document agent purposes clearly**. Future administrators need to understand what each agent can access and why.
"Finance Guidelines Agent - accesses finance-guidelines database only - for Finance department" is clear. "Helper Agent"
is not.
**Review access regularly**. People change roles. Departments reorganize. The agent that made sense six months ago might
grant inappropriate access now.
:::
## What about service access?
You might wonder why managers have access to the knowledge service but department users don't.
Services are administrative capabilities. The knowledge service lets you see ingestion logs, parsed documents, chunking
details, and database metadata. This information is useful for troubleshooting and monitoring but meaningless for end
users.
A user in the Finance tenant doesn't need to know that 47 documents are in the `finance-guidelines` database or that the
last ingestion happened Tuesday at 3 AM. They need to ask the Finance Guidelines Agent questions and get answers.
Services are for administrators. Agents are for users. This separation keeps the interface clean for users while giving
administrators the tools they need.
## Designing your tenant structure
With this understanding, you can design your tenants intentionally:
**Sysadmin tenant**: Full service access, can deploy agents and processes, can create knowledge databases and pipelines.
These are technical experts who maintain the platform infrastructure.
**Management tenant**: Access to administrative services (user management, tenant management, agent instance creation,
evaluation services). Can configure but can't deploy. These are business administrators who manage the platform for
users.
**Department tenants**: Access to relevant agents only, no service access, no administrative capabilities. These are end
users who accomplish work through agents.
You can add layers or modify this structure. Maybe your Legal department needs some administrative capabilities within
their own tenant. Maybe executives need their own tier with special agents. The structure adapts to your needs.
The key is understanding that **data access flows through agents**, not directly to users. Design your agents first
based on data access requirements, then assign agents to tenants based on who should be able to use those capabilities.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/16_multi_tenancy/2_creating_tenants.md
title: "Setting up tenants"
description: ""
---
# Setting up tenants
Creating tenants requires planning your organizational structure and understanding what each group of users should be
able to do. This chapter walks through practical approaches to tenant design.
## Planning your tenant structure
Start by identifying the groups that need separated workspaces. Common patterns:
**Organizational hierarchy**: Sysadmins, managers, and departments each get their own tenant. This separates technical
operations from business administration from daily work.
**Business units**: Marketing, Sales, Engineering, Operations each get a tenant. They share some resources (company-wide
agents) but have unit-specific agents.
**Customer isolation**: Each customer gets their own tenant. Useful for service providers or SaaS deployments where
customers must never see each other's data or agents.
**Environment separation**: Development, staging, and production run as separate tenants. Developers can experiment in
the dev tenant without affecting production users.
**Compliance boundaries**: Legal requirements might mandate separation between entities, geographic regions, or data
classifications. Each regulated boundary becomes a tenant.
## The three-level pattern
Most deployments benefit from three levels of tenants:
```mermaid
graph TD
T1[Level 1: Sysadmin Tenant]
T2[Level 2: Management Tenant]
T3A[Level 3: Finance Tenant]
T3B[Level 3: HR Tenant]
T3C[Level 3: Legal Tenant]
T1 -->|creates & deploys| Agents[Agent Classes]
T1 -->|configures| Pipelines[Data Pipelines]
T2 -->|creates instances| AgentInst[Agent Instances]
T2 -->|creates & manages| T3A
T2 -->|creates & manages| T3B
T2 -->|creates & manages| T3C
AgentInst -->|assigned to| T3A
AgentInst -->|assigned to| T3B
AgentInst -->|assigned to| T3C
style T1 fill:#ffebee
style T2 fill:#fff3e0
style T3A fill:#e8f5e9
style T3B fill:#e8f5e9
style T3C fill:#e8f5e9
```
### Level 1: System administration
Create a tenant for people who maintain the platform infrastructure. These users:
- Deploy agent and process code to the platform
- Configure data pipelines for ingesting documents
- Manage platform services and monitoring
- Have full access to all platform capabilities
This is typically 2-5 people: your DevOps team, lead developers, or IT staff responsible for the platform.
### Level 2: Business administration
Create a tenant for people who administer the platform for business users. These users:
- Create and configure new tenants
- Add users to tenants and assign roles
- Create agent instances from deployed classes
- Monitor agent performance and run evaluations
- Manage knowledge databases (view ingestion status, troubleshoot issues)
These are business analysts, project managers, or department heads who understand the organization's needs but don't
write code.
> In the current implementation, **creating and configuring tenants** (Step 1–3 below) requires the `AIHubSysAdmin`
> realm role. Grant it to the people who fill this Level 2 role so they can run the tenant lifecycle; the remaining
> Level 2 activities (agent instances, evaluations, knowledge) are ordinary in-tenant admin tasks.
### Level 3: End users
Create tenants for people who use agents to accomplish work. These users:
- Chat with agents relevant to their role
- Participate in processes that involve their department
- Cannot see administrative interfaces or create agent instances
This is everyone else in your organization.
## Creating a tenant
A tenant lives in **two stores**: a Keycloak group under `/tenants/` (the source of truth for the tenant's existence
and membership) and the platform's own metadata store (display name, description, access rules). Creating a tenant
therefore means **first creating the Keycloak group, then attaching metadata to it** from the Tenant Administration UI.
The order matters — you cannot configure a tenant whose Keycloak group does not yet exist.
::: warning Required role: `AIHubSysAdmin`
Tenant administration is gated by the **`AIHubSysAdmin`** Keycloak realm role. Only users carrying this realm role see
the Tenant Administration entry points and can reach the create/configure flow. A regular tenant admin role (e.g.
`AIHubAdmin`) is **not** sufficient — that role administers _within_ a tenant, not the tenant lifecycle itself.
:::
### Step 1 — Create the Keycloak group (prerequisite)
Open the **Keycloak admin console**, switch to the **Swiss AI-Hub (`aihub`) realm**, and go to **Groups → `tenants`**.
On the **Child groups** tab, click **Create group** and give it the tenant's ID — for example `finance`. This creates
the group `/tenants/finance`.

The group ID becomes the tenant's immutable identifier. Use a short, lowercase, URL-safe slug (`finance`, `acme-corp`,
`production`); the human-readable display name is set later as metadata.
> Tenant groups are also created automatically by IDP-to-tenant mappings (see _IDP-based tenant assignment_). Those
> groups arrive **Unconfigured** and show up in exactly the same configure flow described below.
### Step 2 — Open Tenant Administration
Tenant Administration is a **separate, sysadmin-only application** served at `sysadmin.` (in local
development: `http://localhost:3334`). You do not need to type that URL — reach it from the main app in one of two ways:
- **Tenant switcher** in the top bar → **Tenant Administration** (shown only to `AIHubSysAdmin` users), or
- the **Tenant Administration** card on the **tenant-selection** screen (`/select-tenant`).

Both land you on the **Tenants** page (`/tenants`), which lists every tenant and its state (Active, Orphaned,
Unconfigured — see [Tenant states](#tenant-states)).
### Step 3 — Configure the tenant
On the **Tenants** page, click the dashed **New Tenant** card. (If the card is disabled, there are no unconfigured
Keycloak groups yet — go back to Step 1.) The **Configure Tenant** dialog attaches metadata to your Keycloak group:

- **Keycloak Tenant ID** — a dropdown of Keycloak groups under `/tenants/` that do not yet have metadata. Select the
group you created in Step 1 (e.g. `finance`).
- **Tenant Name** — the human-readable display name users see when selecting a tenant ("Finance Department", "Customer —
Acme Corp", "Production"). Must be unique.
- **Description** — what the tenant is for. Helps current and future administrators understand its purpose.
- **Access Rules** — optional rules that define the **maximum scope** for everyone in the tenant (see
[Scoping tenants](#scoping-tenants)). You can leave this empty and refine it later.

Click **Save**. The platform seeds the tenant's default roles, adds the current sysadmin to the tenant, and writes the
metadata. The tenant immediately becomes **Active** and is selectable by its members.

> The **Overview** tab also holds the tenant's **chat disclaimer**: a short notice shown below the chat input in
> OpenWebUI, in German, English, French and Italian, up to 100 characters per language. Leave it unset and the tenant
> shows the platform's translated default. The notice follows the user's language, falling back to German and then to
> the first translation that exists.
> There is no separate "Scope" field on the create form. Tenant boundaries are expressed through **Access Rules**, which
> you can set at creation time or edit later from the tenant's **Overview** tab.
## Scoping tenants
A tenant's **access rules** (the **Access Rules** field on the configure form, editable later from the **Overview** tab)
determine the maximum access anyone in that tenant can have. Think of them as drawing a boundary around resources.
For a Finance department tenant, you might scope it to:
- Finance-related agents (reporting agents, guideline agents, approval workflows)
- Finance knowledge databases (for managers who troubleshoot ingestion issues)
- Specific processes (budget approval workflow, expense reporting)
A user in the Finance tenant cannot access HR agents even if you give them an admin role within Finance tenant. The
tenant boundary prevents it.
### Starting broad vs starting narrow
You have two approaches:
**Broad scope**: Give the tenant access to everything initially. Users can see all agents, all services, all processes.
As you learn what they actually need, narrow the scope by removing access to unused resources.
**Narrow scope**: Give the tenant access only to specific resources. As users request more capabilities, expand the
scope gradually.
Broad scope is easier initially but requires more cleanup later. Narrow scope is more secure but users will request
access as they discover needs.
For end-user tenants (departments, customers), start narrow. These users don't need wide visibility - they need specific
tools. For management tenants, start broad since these users need flexibility to administer the platform.
## Configuring roles within tenants
Saving a new tenant **seeds a set of default roles automatically**, so the tenant is usable immediately. To review or
extend them, open the tenant from the Tenants list and switch to its **Roles** tab (`/tenants//roles`). Roles define
what users can do within the tenant's boundaries; each role carries its own access rules, bounded by the tenant's.
For a department tenant, you might add:
**Standard User**: Can chat with department agents, participate in processes, but cannot create or modify anything.
**Team Lead**: Can create agent instances, configure agents for their team, view evaluation results, but cannot manage
users or change tenant settings.
**Department Admin**: Full control within the department tenant - can add users, assign roles, create agents, and manage
all department resources.
These roles only apply within this tenant. A user who is "Department Admin" in Finance has no privileges in the HR
tenant unless explicitly granted.
## Assigning roles to users
Open the tenant and switch to its **Users** tab (`/tenants//users`). It lists the users known to the platform; for
each one you **assign or revoke that tenant's roles**.
User _lifecycle_ — creating accounts, deleting them, federating from an external identity provider — is managed in
**Keycloak**, not here. A user becomes a tenant member through Keycloak group membership (the `/tenants/` group),
which is also how IDP-based mappings add users automatically. The Users tab is where you grant those members their
in-tenant roles.
Users can belong to multiple tenants. Someone might be:
- Standard user in their department tenant
- Team lead in a cross-functional project tenant
- Admin in a test tenant for trying out new features
## Startup tenant behavior
When someone logs in for the first time, they automatically join the startup tenant (the one the platform seeded on
first boot — see `AIHUB_STARTUP_TENANT_*`) with standard user roles. Configure this behavior with environment variables:
```bash
AIHUB_USER_SIGNUP_DEFAULT_TENANT="default"
AIHUB_USER_SIGNUP_DEFAULT_ROLES="AIHubUser"
FIRST_AIHUB_USER_SIGNUP_DEFAULT_ROLES="AIHubAdmin"
```
The first person to sign in receives admin roles, ensuring someone can administer the platform immediately. Subsequent
users receive standard roles.
This automatic assignment only happens once per user. After that, explicitly add them to other tenants as needed.
## Common configurations
### Single company with departments
Sysadmin tenant → Management tenant → One tenant per department
Managers create department tenants and configure which agents each department can access. Department users only see
their department's resources.
### Multi-customer SaaS
Sysadmin tenant → Management tenant → One tenant per customer
Each customer tenant is isolated. Customers cannot see other customers' agents or data. Managers onboard new customers
by creating a tenant, configuring relevant agents, and adding that customer's users.
### Consulting organization
Sysadmin tenant → Management tenant → One tenant per client project
Project tenants give consultants access to client-specific agents and knowledge. When a project ends, archive the
tenant. When a consultant joins a new project, add them to that project's tenant.
### Development workflow
Sysadmin tenant → Dev tenant → Staging tenant → Production tenant
Developers work in dev tenant with relaxed controls. Staging tenant mirrors production for testing. Production tenant
has strict access controls. Same agents, same code, different tenants for different purposes.
## Tenant states
Because tenants live in two stores — Keycloak (group under `/tenants/`, the source of truth for existence) and the
platform's own metadata store (display name, description, access rules) — they can be observed in three states:
- **Active**: both the Keycloak group and the metadata are present. End users can reach the tenant.
- **Orphaned**: metadata is present but the Keycloak group is gone (e.g. an operator removed the group out of band). End
users cannot reach the tenant. Sysadmins see it as read-only with a delete-metadata action only.
- **Unconfigured**: the Keycloak group exists but no metadata has been attached. End users cannot reach the tenant yet.
Sysadmins see it in the "configure tenant" flow, where attaching metadata promotes it to Active.
The Tenant Administration app (`sysadmin.`, route `/tenants`) surfaces all three states. The
configure-tenant action attaches metadata to an Unconfigured group and is the primary path through which an
operator-created Keycloak group (e.g. for an IDP-to-tenant mapping) becomes a usable tenant.
## Tenant lifecycle
Tenants persist until explicitly deleted. You can:
**Archive a tenant**: Remove all users but keep the tenant and its roles. Useful for completed projects or inactive
customers. No one can access it but the configuration remains if you need to reactivate it.
**Delete a tenant**: Remove the platform's metadata for the tenant. The Keycloak group itself is left untouched —
cleanup of the group is a separate step in the Keycloak admin console. Deletion is permanent on the metadata side.
::: warning Last-Tenant Protection
The platform requires at least one tenant to remain. Deletion is blocked when it would leave the system with no
remaining tenants. Any tenant — including the one the platform seeded on first boot — can be deleted as long as at least
one other tenant exists. The startup tenant carries no database-level marker that distinguishes it from tenants
configured later.
:::
## Practical tips
::: tip Best Practices
**Document your decisions**: Write down why you created each tenant and what its scope should be. Six months later when
roles have changed, this documentation prevents confusion.
**Start simple**: One management tenant and one end-user tenant works for many organizations. Add complexity only when
you need it.
**Test with restricted users**: Create a test account, add it to a new tenant, and verify what that user can see. Don't
assume - verify.
**Review quarterly**: People change roles. Projects end. New departments form. Your tenant structure should evolve with
your organization.
**Plan for growth**: "Customer 1" works when you have three customers. "Customer - Acme Corp" works when you have three
hundred.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/16_multi_tenancy/3_user_management.md
title: "Managing users and roles"
description: ""
---
# Managing users and roles
Users access the platform through your identity provider (Azure AD, Google Workspace, Okta, etc.), but their roles and
tenant memberships are managed within the platform itself.
## User lifecycle
### First login
::: info Authentication vs Authorization
When someone logs in for the first time:
1. Your identity provider verifies who they are
2. The platform reads their identity (name, email) from Keycloak — no local user record is created
3. They automatically join the default tenant with standard user roles
4. They see the default tenant's agents and resources
The platform also creates the user's chat account in OpenWebUI as soon as they hold a role in their active tenant, and
places it in that tenant's groups. The model picker is therefore populated at the first chat visit, without waiting for
an administrator to change anything.
**Important**: The platform doesn't import roles from your identity provider. It only imports identity information (who
you are, not what you can do).
:::
### Adding to additional tenants
After someone's first login, administrators can add them to other tenants. Navigate to **Service** → **Tenants**, select
a tenant, and click **Add User** on the Users tab.
Search for the user by name or email. Select which roles to assign in this tenant. The user can now switch to this
tenant and will have the permissions defined by those roles.
Users don't need to log out and back in. Changes take effect on their next request.
### Removing from tenants
Click **Remove** next to a user in the tenant's user list. This removes their membership entirely - they lose all roles
in that tenant and cannot select it anymore.
Removing someone from the default tenant is unusual but allowed. Users removed from all tenants cannot access any
platform resources.
## Roles
Roles are collections of permissions scoped to a specific tenant. The "Manager" role in the Finance tenant is completely
separate from a "Manager" role in the HR tenant.
### Creating roles
Navigate to a tenant's **Roles** tab and click **Create Role**.
Choose a descriptive name: "Agent User", "Document Reviewer", "Department Admin". The name should explain what the role
allows.
Define what this role permits. For a department user role:
- Access to department agents
- Access to department processes
- Cannot see administrative services
- Cannot see other departments' resources
For a department admin role:
- Everything department users can do
- Create agent instances
- Manage knowledge ingestion
- Add users to the department tenant
- Assign roles to department users
### Role scope within tenant boundaries
Roles grant permissions up to the tenant's boundary. If the tenant can only access Finance agents, a role in that tenant
cannot grant access to HR agents even if configured to do so.
The tenant defines what exists. Roles define what users with that role can do within what exists.
### Changing roles
Select a role and click **Edit**. Changes affect all users with that role immediately.
Adding permissions to a role grants those permissions to everyone who has it. Removing permissions revokes them from
everyone with that role.
Test role changes carefully, preferably in a test tenant first.
## Managing user roles
Click **Manage Roles** next to a user in the tenant's user list.
Add roles by selecting from available roles in that tenant. Remove roles by deselecting them.
Users can have multiple roles in the same tenant. Their effective permissions are the union of all their roles. If one
role grants access to agents and another grants access to processes, they can access both.
A user with no roles in a tenant is technically allowed but serves no purpose.
## Bulk operations
### CSV import
::: details CSV Import Format
Import many users at once through **Service** → **Users** → **Import Users**.
Create a CSV file with three columns:
- email
- name
- initial_roles (comma-separated role names)
Example:
```csv
email,name,initial_roles
john.doe@company.com,John Doe,"StandardUser,AgentUser"
jane.smith@company.com,Jane Smith,StandardUser
```
The import:
- Creates user profiles if they don't exist yet
- Adds users to your current tenant (determined by which tenant you're in)
- Assigns the specified roles
Users who haven't logged in yet receive placeholder profiles that update when they first authenticate.
:::
### Bulk role assignment
Select multiple users using checkboxes in the user list. Choose **Bulk Actions** → **Assign Roles**.
Three modes:
- **Add**: Add selected roles to users' existing roles
- **Remove**: Remove selected roles, keeping others
- **Replace**: Replace all roles with selected roles
Changes apply immediately to all selected users.
## Cross-tenant membership
Users often belong to multiple tenants with different roles in each.
Someone might be:
- Admin in Development tenant (can deploy and configure freely)
- Standard user in Staging tenant (can test but not modify)
- Viewer in Production tenant (read-only access)
They switch between tenants using the selector in the top bar. The interface shows only what's available in the selected
tenant. Their permissions change based on their roles in that tenant.
## Permissions inheritance
Permissions don't inherit between tenants. Being an admin in one tenant grants no privileges in another tenant.
Permissions don't inherit within role hierarchies. Having user access doesn't automatically grant admin access.
## Common role structures
### Department tenant
**Standard User**: Chat with department agents, participate in processes, view results
**Power User**: Standard user abilities plus create agent instances, configure agents, run evaluations
**Department Admin**: Power user abilities plus manage users, assign roles, create department-specific roles
### Management tenant
**Tenant Manager**: Create new tenants, configure tenant boundaries, assign agents to tenants
**User Administrator**: Add users to tenants, assign roles, manage user access across all tenants
**Platform Administrator**: Full administrative access to all services and configurations
### Development tenant
**Developer**: Deploy agents, create pipelines, modify configurations, full service access for testing
**QA**: Create test agent instances, run evaluations, view all agents, cannot deploy code
**Observer**: Read-only access to all resources for learning and documentation
## Best practices
::: tip User Management Best Practices
**Principle of least privilege**: Grant users the minimum permissions needed for their work. You can always expand
access later.
**Regular audits**: Review user roles quarterly. People change positions. Access granted for a project might not be
appropriate six months later.
**Clear role names**: "Finance Standard User" is clearer than "FSU". Future administrators will thank you.
**Test before rolling out**: Create a test user, add them to a tenant with a new role, and verify they see exactly what
you intend.
**Document role purposes**: When creating a role, document why it exists and what its responsibilities are. This
prevents confusion when reviewing access later.
**Limit admin roles**: Admin users can grant themselves any permission within their tenant. Only trusted personnel
should have admin roles.
:::
## Troubleshooting access issues
When a user reports they can't access something:
1. Verify they selected the correct tenant (check the tenant selector in the top bar)
2. Check if their tenant has access to that resource
3. Verify they have roles assigned in that tenant
4. Review what those roles permit
The most common issue is users working in the wrong tenant. The second most common is the tenant boundary preventing
access even though the role would permit it.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/16_multi_tenancy/4_access_control.md
title: "Technical reference - Access control"
description: ""
---
# Technical reference: Access control
This chapter documents the technical details of how the platform enforces access control. This information is useful for
system administrators configuring tenants and roles, and for developers extending the platform.
## Access rule format
Access rules use a hierarchical pattern: `aihub.[user|admin]...`
Examples:
```
aihub.user.agent.> # All agents (user access)
aihub.admin.agent.research.* # All research agents (admin access)
aihub.user.knowledge.hr-docs.policies # Specific knowledge namespace
aihub.admin.service.tenant # Tenant management service
```
### Wildcards
**Single-level** (`*`) matches exactly one segment:
- `agent.research.*` matches `agent.research.instance-1` but not `agent.research.team.instance-1`
**Multi-level** (`>`) matches one or more segments at the end:
- `agent.>` matches `agent.research.instance-1`, `agent.analysis.team.special`, and any other agent path
- Must be the last token in the rule
### Admin vs user rules
Rules starting with `aihub.admin.*` grant administrative access. Users with admin access automatically have equivalent
user access.
A user with `aihub.admin.agent.>` can access resources requiring either `aihub.admin.agent.*` or `aihub.user.agent.*`.
### The chat interface is a separate axis
The rules above govern the platform API. The chat interface has its own administrator role, granted by the
`AIHubSysAdmin` Keycloak realm role rather than by any tenant role or access rule. Holding `aihub.admin.*` in a tenant
does not make you a chat administrator, and being a chat administrator does not grant visibility into other users'
uploads or conversations - those are scoped to their owner by default. See
[ADR: Scope OpenWebUI Admins to Their Own Files and Chats](/arc42/decisions/2026_09_07_openwebui_admin_scoped_to_own_data.md).
## Permission resolution
When a request arrives, the platform:
1. Extracts the user's identity from the authentication token
2. Reads the `X-Tenant-Id` header to determine the tenant context
3. Queries the user's roles within that specific tenant
4. Collects all access rules from those roles
5. Retrieves the tenant's access rules
6. Checks if both the tenant and the user permit the requested action
```mermaid
sequenceDiagram
participant User
participant API
participant Database
User->>API: Request with X-Tenant-Id
API->>Database: Get user roles in tenant
Database-->>API: Role IDs
API->>Database: Get access rules for roles
Database-->>API: User access rules
API->>Database: Get tenant access rules
Database-->>API: Tenant access rules
API->>API: Check both rule sets
API-->>User: Success or 403 Forbidden
```
### Two-layer checking
Access requires passing both layers:
**Layer 1: Tenant boundary** - Does the tenant allow this resource at all?
If the tenant's access rules don't include the requested resource, access is denied immediately without checking user
roles.
**Layer 2: User permissions** - Does the user's role permit this action?
After confirming the tenant allows the resource, the system checks if the user's roles grant the required permission.
Both must pass for access to be granted.
### Example
Tenant has: `aihub.user.agent.research.*`
User has: `aihub.user.agent.>` (from their role)
User requests: `aihub.user.agent.research.instance-1`
- Tenant check: ✓ (tenant allows research agents)
- User check: ✓ (user role allows all agents)
- Result: Access granted
User requests: `aihub.user.agent.finance.instance-1`
- Tenant check: ✗ (tenant only allows research agents)
- Result: Access denied (user check not evaluated)
## Service-level permissions
Each service requires a base permission: `aihub.user.service.`
Before checking resource-specific permissions, the system verifies the user has access to the service itself.
To access an agent, you need:
- Service access: `aihub.user.service.agent`
- Resource access: `aihub.user.agent..`
If the tenant doesn't grant service access, no resources in that service are accessible regardless of other rules.
## Path parameter substitution
Permission templates use placeholders that resolve from the request:
Template: `aihub.user.agent.{agent_class}.{agent_id}`
Request: `GET /api/v1/agents/research/instance-alpha`
Resolved permission: `aihub.user.agent.research.instance-alpha`
The system checks this concrete permission against the user and tenant access rules.
## Access levels
The system returns three levels:
**ACCESS_DENIED**: No permission. Returns HTTP 403.
**ACCESS_USER**: User-level access to view and interact with the resource.
**ACCESS_ADMIN**: Admin-level access to modify, configure, or delete the resource.
Controllers can differentiate between user and admin access for audit purposes, though many operations only check
whether access is granted (not denied).
## Configuration through environment
Configure default behavior through environment variables:
```bash
# Startup tenant (seeded on first boot; an ordinary tenant thereafter).
# Leave ACCESS_RULES empty to derive the ceiling from the models this instance serves,
# minus AIHUB_TENANT_DEFAULT_ACCESS_EXCLUDED_MODELS. Set "aihub.admin.>" for unrestricted
# access, which also skips the model-gateway lookup at first boot.
AIHUB_STARTUP_TENANT_NAME="Swiss AI Hub"
AIHUB_STARTUP_TENANT_ACCESS_RULES=""
AIHUB_TENANT_DEFAULT_ACCESS_EXCLUDED_MODELS="text-generation/Apertus-70B-Instruct-2509"
# Agent blueprints a new tenant's ceiling grants. Every other blueprint stays hidden from that
# tenant until a sysadmin grants it.
AIHUB_TENANT_DEFAULT_ACCESS_AGENT_CLASSES="LLMWrappingAgent,FewShotAgent,RAGAgent"
# Automatic user signup
AIHUB_USER_SIGNUP_DEFAULT_TENANT="default"
AIHUB_USER_SIGNUP_DEFAULT_ROLES="AIHubUser,AIHubAgentUser"
FIRST_AIHUB_USER_SIGNUP_DEFAULT_ROLES="AIHubAdmin"
```
### What a new tenant starts with
A tenant created with no explicit rules does not get a wildcard. Its ceiling is derived, and the two curated families
behave differently:
**Models** are read from the live gateway roster. A capability with nothing excluded collapses to a wildcard
(`aihub.user.model.embedding.>`), so models added to it later reach new tenants unattended. A capability holding an
exclusion enumerates its survivors instead.
**Agents** are named from a configured list — the standard blueprint set. Every other blueprint stays hidden from that
tenant: it is absent from Admin → Agents rather than shown and refused, because the blueprint list is filtered by the
same per-class rules. One rule is granted per class, the bare `aihub.admin.agent.`, which is what creating an
assistant is guarded on.
What the tenant gets is the blueprint, not the assistants already built from it. Assistants are stored in one collection
shared by the whole deployment, so `aihub.admin.agent..>` would mean every assistant of that type anywhere —
including other tenants'. A new tenant therefore starts with its three blueprints and an empty assistant list, and each
assistant it creates is added to its own ceiling as it is created. Granting a tenant one specific assistant that already
exists stays a deliberate act: tick that assistant's own row in the tenant editor.
**Knowledge** follows the same reasoning. The tenant gets `aihub.admin.knowledge`, which is what creating a knowledge
database is guarded on, and no existing database. Databases are also stored deployment-wide, so
`aihub.admin.knowledge.>` would hand the tenant every other tenant's knowledge. Each database a tenant creates is added
to its own ceiling as `aihub.admin.knowledge.` plus `aihub.admin.knowledge..>` — the second form covers every
folder in it, including folders a sync or ingestion pipeline creates later — and the creator gets a `KnowledgeAdmin`
role holding the same two rules to hand on to others. A database created by a sysadmin outside any tenant, and the
legacy `defaultknowledge` / `sharedknowledge` databases, reach a tenant only once the sysadmin ticks them in its tenant
editor.
Agents use an allow list where models use exclusions because the two rosters differ in kind. Model names vary between
CPU and GPU deployments, so a fixed list would leave a GPU tenant with no chat model. Agent class names are fixed at
build time, and the discovered-class roster is still empty when the startup tenant is seeded at first boot — an
exclusion would have nothing to subtract from and would grant that tenant no agents at all.
Both settings are seeds, read once while the ceiling is computed. Afterwards the tenant's stored rules are the only
authority, so granting a further blueprint or model is an ordinary access-rule edit in the tenant editor — no redeploy,
and the setting has no say in it. Existing tenants are untouched and keep whatever ceiling they already have.
## Sysadmin access
Users with the Keycloak realm role `AIHubSysAdmin` receive implicit admin access to every tenant and every resource. The
two-stage tenant/user check described above is bypassed — a sysadmin is treated as an admin everywhere.
Sysadmins can also act without a tenant context, which enables cross-tenant endpoints such as the tenant administration
UI. Every sysadmin is a real Keycloak user with a real user id, so their actions remain traceable in Langfuse and they
appear in tenant member listings like any other user.
Assign the `AIHubSysAdmin` realm role in Keycloak directly or via identity provider mappers. The platform also seeds a
dedicated Superuser account from `SUPERUSER_EMAIL` / `SUPERUSER_PASSWORD` (the username is seeded equal to
`SUPERUSER_EMAIL`, so this account logs in with its email) and materializes `SUPERUSER_TOKEN` as a bearer token for that
user so internal services can call the API without a browser session.
Use sparingly — sysadmin access exists for platform administration, not day-to-day operations.
## Validation rules
::: warning Access Rule Format Requirements
When creating access rules:
**Required format**:
- Must start with `aihub.user.` or `aihub.admin.`
- Only lowercase letters, numbers, dots, hyphens, underscores, `*`, `>`
- Multi-level wildcard `>` only at the end
**`>` does not match its own root**:
`aihub.admin.knowledge.>` matches `aihub.admin.knowledge.hr-docs` but **not** the bare `aihub.admin.knowledge` — `>`
requires at least one further segment. Some permissions are guarded on a bare root precisely because the resource does
not exist yet: creating a knowledge database is checked against `aihub.admin.knowledge`, since a database that has not
been created cannot be named by a rule. A rule set that should cover both has to carry both forms, which is why
`AIHubKnowledgeAdmin` is seeded with `aihub.admin.knowledge` *and* `aihub.admin.knowledge.>`. This applies to tenant
ceilings as much as to roles: a ceiling holding only the `.>` form caps the root permission away from every role in the
tenant. The reverse holds one level down: `aihub.admin.knowledge.hr-docs` alone does not reach the folder
`aihub.admin.knowledge.hr-docs.policies`, which is why a newly created database is granted with its `.>` form too.
**Prohibited**:
- Uppercase letters
- Special characters other than `.`, `-`, `_`, `*`, `>`
- `>` in the middle of a rule
The system validates rules when creating or editing tenants and roles. Invalid rules trigger an error with the specific
problem.
:::
## Common patterns
### Broad platform access
```
aihub.admin.>
```
Full admin access to everything. Use for sysadmin tenants.
### Service administrators
```
aihub.admin.service.user
aihub.admin.service.role
aihub.admin.service.tenant
```
Can manage users, roles, and tenants but not other services.
### Department access
```
aihub.user.agent.department-finance.*
aihub.user.knowledge.finance-docs.>
aihub.user.process.finance-workflows.*
```
Access to finance-specific resources only.
### Read-only access
```
aihub.user.agent.>
aihub.user.knowledge.>
```
Can view and use agents and knowledge but cannot create or modify.
### Power user
```
aihub.user.>
aihub.admin.agent..*
aihub.admin.knowledge.-docs.>
```
User access everywhere, admin access only to department resources.
## Debugging access issues
::: details Troubleshooting Checklist
When troubleshooting, check these in order:
1. **Tenant selection**: Verify the user selected the intended tenant
2. **Tenant boundary**: Confirm the tenant's access rules include the resource
3. **User membership**: Verify the user belongs to the tenant
4. **Role assignment**: Check the user has roles in that tenant
5. **Role rules**: Review what those roles permit
6. **Service access**: Verify service-level permission exists
The platform returns detailed error messages indicating which permission failed. Use this to identify the missing rule.
:::
## Performance notes
Access checking is optimized:
- Rules are compiled once per request
- Multiple permission checks for the same user reuse the compiled rules
- Complex wildcard patterns have minimal performance impact
- Role changes take effect immediately without cache delays
Switching tenants triggers a full cache invalidation in the frontend, causing data to be refetched.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/16_multi_tenancy/5_idp_based_tenant_assignment.md
title: "IDP-based tenant assignment"
description: ""
---
# IDP-based tenant assignment
By default, all users who log in to the platform are automatically assigned to the **default tenant**. Administrators
then manually add users to additional tenants through the Keycloak admin console. This works well for small deployments,
but becomes impractical when you have multiple organizations, each authenticating through their own identity provider.
IDP-based tenant assignment automates this. When a user authenticates through a specific identity provider, Keycloak
automatically assigns them to the corresponding tenant. A user who logs in through Company A's Azure AD app registration
joins Company A's tenant. The same user logging in through Company B's app registration also joins Company B's tenant.
Both memberships persist — they accumulate rather than replace each other.
::: warning IDP-based tenant assignment must be set up specifically
IDP-to-tenant mapping is not enabled by default. It's a capability the installation team configures per deployment. The
default behavior is manual tenant assignment through the Keycloak admin console.
:::
## How it works
Tenant memberships in Keycloak are stored as **group memberships**. Each tenant has a corresponding group under the
`/tenants/` parent group in Keycloak. When a user belongs to `/tenants/company-a`, the platform knows they're a member
of the Company A.
```mermaid
graph TD
subgraph Keycloak Groups
TG["/tenants"]
TG --> D["/tenants/default"]
TG --> A["/tenants/company-a"]
TG --> B["/tenants/company-b"]
end
subgraph Identity Providers
IDP1["Azure AD - Company A"]
IDP2["Azure AD - Company B"]
end
subgraph Users
U1["alice@company-a.com"]
U2["bob@company-b.com"]
U3["carol@both.com"]
end
IDP1 -- "Hardcoded Group mapper" --> A
IDP2 -- "Hardcoded Group mapper" --> B
U1 --> D
U1 --> A
U2 --> D
U2 --> B
U3 --> D
U3 --> A
U3 --> B
style TG fill:#f5f5f5
style D fill:#e8f5e9
style A fill:#e1f5ff
style B fill:#fff3e0
```
A **Group Membership protocol mapper** on the `tenants` client scope includes these group memberships in the OIDC token
as a `tenants` claim:
```json
{
"tenants": ["/tenants/default", "/tenants/company-a"]
}
```
The application reads this claim to determine which tenants the user belongs to.
## Default tenant assignment
::: info Every User Gets the Default Tenant
The `/tenants/default` group is configured as a **Keycloak default group**. Every new user — regardless of how they
authenticate — is automatically added to this group on first login.
This means all users start with access to the default tenant. No manual assignment is needed for the baseline
experience.
:::
The default tenant typically provides access to shared, company-wide agents and resources. Users who need access to
additional tenants are either assigned manually by administrators or automatically through IDP-based mapping.
## Setting up IDP-to-tenant mapping
IDP-to-tenant mapping is not enabled by default. It's a capability the installation team configures per deployment. The
default behavior is manual tenant assignment through the Keycloak admin console.
### Prerequisites
Before configuring IDP-to-tenant mapping:
1. Create the tenant group in Keycloak under `/tenants/` (e.g., `/tenants/company-a`)
2. Create the corresponding tenant in the Swiss AI Hub application
3. Configure the identity provider in Keycloak with the customer's Azure AD app registration credentials
### Option 1: Hardcoded group mapping (one IDP = one tenant)
This is the simplest approach. Every user who authenticates through a specific identity provider is automatically
assigned to a specific tenant. Use this when each Azure AD app registration represents a distinct organization or
customer.
Add a **Hardcoded Group** IDP mapper to the identity provider:
```json
{
"name": "tenant-mapper-company-a",
"identityProviderAlias": "azure-ad-company-a",
"identityProviderMapper": "oidc-hardcoded-group-idp-mapper",
"config": {
"syncMode": "INHERIT",
"group": "/tenants/company-a"
}
}
```
This mapper runs every time a user authenticates through `azure-ad-company-a`. It adds the user to `/tenants/company-a`
without removing any existing group memberships.
::: tip Why Groups Instead of Attributes?
Keycloak's attribute IDP mappers **overwrite** values on each login. If a user logs in via IDP-A and gets
`tenant = company-a`, then logs in via IDP-B, the attribute changes to `tenant = company-b` — the first assignment is
lost.
Group memberships are **additive**. The Hardcoded Group mapper adds the user to a group without removing them from other
groups. This is why tenant assignment uses groups, not user attributes.
:::
::: warning Mapper Provider ID
The Keycloak provider ID for the Hardcoded Group IDP mapper is `oidc-hardcoded-group-idp-mapper` (with the `oidc-`
prefix). Using `hardcoded-group-idp-mapper` (without prefix) will cause a `NullPointerException` during IDP login.
:::
#### Multiple IDPs, multiple tenants
When you have three Azure AD app registrations (one per customer), configure each with its own Hardcoded Group mapper:
| Identity Provider | Hardcoded Group Mapper | Tenant Group |
| -------------------- | ------------------------- | -------------------- |
| `azure-ad-company-a` | `tenant-mapper-company-a` | `/tenants/company-a` |
| `azure-ad-company-b` | `tenant-mapper-company-b` | `/tenants/company-b` |
| `azure-ad-company-c` | `tenant-mapper-company-c` | `/tenants/company-c` |
A user with the same email across all three app registrations will be merged into a single Keycloak account (via the
first broker login flow) and accumulate all three tenant memberships plus the default tenant.
### Option 2: Claim-based group mapping (groups from the IDP determine the tenant)
Sometimes you don't want a blanket mapping of "everyone from this IDP joins this tenant." Instead, you want to map based
on a claim from the identity provider — for example, Azure AD group memberships.
Use an **Advanced Claim to Group** IDP mapper to map a specific claim value to a tenant group:
```json
{
"name": "tenant-mapper-finance-group",
"identityProviderAlias": "azure-ad",
"identityProviderMapper": "oidc-advanced-group-idp-mapper",
"config": {
"syncMode": "INHERIT",
"claims": "[{\"key\": \"groups\", \"value\": \"finance-department-group-id\"}]",
"group": "/tenants/finance"
}
}
```
This mapper checks if the `groups` claim from Azure AD contains `finance-department-group-id`. If it does, the user is
assigned to `/tenants/finance`.
::: warning Azure AD Group Claims Require Configuration
By default, Azure AD does not include group memberships in the ID token. You must configure this in the Azure AD app
registration:
1. Go to **Token configuration** in the Azure Portal
2. Click **Add groups claim**
3. Select **Security groups** (or **All groups**)
4. Under **Customize token properties by type**, ensure the **ID** token includes groups
5. Note: Azure AD sends group **Object IDs** (UUIDs), not group names. Use the Object ID as the claim value in the
mapper.
:::
#### Mapping multiple Azure AD groups to tenants
You can add multiple Advanced Claim to Group mappers on the same identity provider, each mapping a different Azure AD
group to a different tenant:
```json
[
{
"name": "tenant-mapper-finance",
"identityProviderAlias": "azure-ad",
"identityProviderMapper": "oidc-advanced-group-idp-mapper",
"config": {
"syncMode": "INHERIT",
"claims": "[{\"key\": \"groups\", \"value\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"}]",
"group": "/tenants/finance"
}
},
{
"name": "tenant-mapper-legal",
"identityProviderAlias": "azure-ad",
"identityProviderMapper": "oidc-advanced-group-idp-mapper",
"config": {
"syncMode": "INHERIT",
"claims": "[{\"key\": \"groups\", \"value\": \"ffffffff-1111-2222-3333-444444444444\"}]",
"group": "/tenants/legal"
}
}
]
```
A user who is a member of both Azure AD groups will be assigned to both `/tenants/finance` and `/tenants/legal`.
### Option 3: Role-based group mapping (IDP roles determine the tenant)
If your Azure AD app registration uses app roles instead of groups, you can map those roles to tenant groups using an
**Advanced Claim to Group** mapper on the `roles` claim:
```json
{
"name": "tenant-mapper-premium-tier",
"identityProviderAlias": "azure-ad",
"identityProviderMapper": "oidc-advanced-group-idp-mapper",
"config": {
"syncMode": "INHERIT",
"claims": "[{\"key\": \"roles\", \"value\": \"PremiumTenant\"}]",
"group": "/tenants/premium"
}
}
```
Users with the `PremiumTenant` app role in Azure AD will be assigned to `/tenants/premium` in addition to the default
tenant.
## User merging across identity providers
When the same person authenticates through multiple identity providers (e.g., the same email across different Azure AD
app registrations), Keycloak merges them into a single user account through the **first broker login flow**.
On first login via a new IDP, Keycloak detects that a user with the same email already exists and links the new identity
to the existing account. The user now has multiple linked identities but a single Keycloak account.
Each IDP's mappers run independently when the user authenticates through that IDP. Because group membership is additive,
a user who logs in through Company A's IDP gets `/tenants/company-a`, and later logging in through Company B's IDP adds
`/tenants/company-b` — without removing the first assignment.
::: warning Trust Your Identity Providers
Automatic account linking trusts that external identity providers have verified the user's email. Only enable this for
trusted IDPs. An attacker controlling an IDP that doesn't verify emails could link to any existing account by claiming a
matching email address.
:::
## Choosing the right approach
| Scenario | Recommended Approach |
| ------------------------------------------------------------ | ----------------------------------------------- |
| Each customer/tenant has their own Azure AD app registration | **Hardcoded Group** per IDP |
| Single Azure AD, tenants based on AD group membership | **Advanced Claim to Group** on `groups` claim |
| Single Azure AD, tenants based on app roles | **Advanced Claim to Group** on `roles` claim |
| Small deployment, few tenants | **Manual assignment** in Keycloak admin console |
You can combine approaches. Use Hardcoded Group mappers for some IDPs and Advanced Claim to Group mappers for others.
The default tenant assignment always applies regardless of which additional mappings are configured.
## What the platform does not do
::: details Responsibilities and Boundaries
**Keycloak stores tenant assignments** — which user belongs to which tenant, represented as group memberships.
**Keycloak does not manage tenants** — what tenants exist, their configuration, settings, and active status is the
responsibility of the Swiss AI Hub application.
**IDP-to-tenant mapping is not a default feature** — the platform provides the mechanism (groups, protocol mappers,
documentation), but the installation team must configure it per deployment. Out of the box, tenant assignment is manual.
**The platform does not synchronize backwards** — if you remove a user from a tenant group in Keycloak, the Swiss AI Hub
application's local role assignments (`UserTenantRoleEntity`) for that user in that tenant are not automatically cleaned
up. Both systems should be kept in sync by administrators.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/17_slack_teams_integrations.md
title: "Slack & Teams Integrations"
description: ""
---
# Slack & Teams Integrations
## Concept and Purpose
The Bot Framework API is a separately deployable service built on FastAPI that brings AI agent capabilities directly
into collaboration platforms where employees work daily - Microsoft Teams, Slack, and web chat interfaces. Rather than
requiring users to switch to specialized AI applications, this integration embeds intelligent assistance within familiar
communication tools, eliminating adoption barriers and accelerating AI utilization across organizations.
This approach addresses a fundamental challenge in enterprise AI adoption: even powerful AI capabilities remain
underutilized if accessing them requires learning new tools or disrupting established workflows. By meeting users where
they already work, the Bot Framework API transforms AI from a separate application into an integrated capability within
existing collaboration infrastructure.
## Core Design Principles
### Embedded AI in Natural Workflows
The platform's design philosophy prioritizes embedding AI capabilities within tools employees use throughout their
workday rather than creating standalone AI applications. Users interact with AI agents through the same Teams channels
or Slack conversations they use for team communication, eliminating context switching and credential management
overhead. Conversations with AI agents integrate seamlessly alongside human discussions, enabling natural collaboration
patterns where AI assistance becomes just another resource available to the team.
This embedding approach provides significant business advantages: IT departments don't need to provision and support
additional applications, users don't require separate training on AI-specific interfaces, AI conversations benefit from
the security and compliance controls already governing collaboration platforms, and usage analytics integrate with
existing collaboration platform metrics.
### Multi-Channel Abstraction
By integrating with Microsoft Azure Bot Service, the platform gains simultaneous access to multiple communication
channels through a single implementation. Azure Bot Service provides a standardized abstraction layer that handles
platform-specific messaging protocols, authentication flows, and rich media formatting. This architecture enables the
Swiss AI Hub to support Microsoft Teams, Slack, web chat widgets, and future channels as Azure Bot Service adds support,
all without platform-specific development efforts for each channel.
Organizations benefit from deployment flexibility: different teams can use their preferred collaboration tools while
accessing identical AI capabilities, geographic or regulatory requirements can be met by deploying different channels in
different regions, and new communication platforms become available as Bot Service adds support without requiring Swiss
Swiss AI Hub platform changes.
### Independent Deployment and Scaling
The Bot Framework API deploys as a separate Docker container independent of the main platform services. This
architectural separation provides operational advantages: bot integration scales independently based on conversation
volume patterns that differ from API request patterns, organizations deploy bot capabilities only where needed rather
than universally, bot-specific configuration and credentials remain isolated from core platform infrastructure, and
updates to bot functionality occur without impacting or requiring coordination with core platform services.
The independent deployment model also supports security isolation: bot credentials and channel configurations remain
separated from main platform secrets, reducing blast radius if channel-specific vulnerabilities emerge, and enabling
different security policies for different communication channels.
## Supported Capabilities
The Bot Framework API enables sophisticated human-AI collaboration through several key capabilities:
**Conversational AI Access**: Users interact with AI agents through natural conversation in their collaboration tools,
receiving either complete responses or progressive streaming updates depending on task complexity. The interface
supports rich media including documents, images, and structured data cards, maintaining conversation context across
multiple interactions. This contextual awareness enables agents to reference previous messages, understand ongoing
projects, and provide relevant assistance based on conversation history.
**Multi-Agent Orchestration**: Users can interact with different specialized agents within the same conversation,
switching between agents based on task requirements or explicitly selecting agents for specific questions. This
flexibility supports workflows where different expertise areas require different agents - financial analysis, legal
review, technical research - without requiring separate conversations or applications.
**Human-in-the-Loop Workflows**: The Bot-in-the-Loop pattern enables AI agents to request human input mid-execution,
posting questions to Slack channels where team members can provide decisions, approvals, or expert guidance. Agent
workflows continue after receiving human responses, enabling sophisticated automation scenarios that combine AI
efficiency with human judgment. This capability supports approval workflows, expert consultations, quality checks, and
disambiguation scenarios where human context determines appropriate next steps.
**Enterprise Integration**: Authentication flows through existing organizational identity providers, ensuring users
access AI with the same credentials and permissions governing collaboration platform access. Conversations automatically
expire based on configurable retention policies, supporting compliance requirements without manual data lifecycle
management.
## Business Value
### Accelerated Adoption and Utilization
By eliminating the need to learn and access separate AI applications, the Bot Framework API dramatically reduces
adoption friction. Employees begin using AI assistance simply by messaging a bot in Teams or Slack, using communication
patterns they already know. Organizations report utilization rates 3-5x higher when AI capabilities integrate into
existing tools compared to standalone AI applications requiring separate access and training.
The embedded approach particularly benefits occasional users who need AI assistance infrequently but significantly -
these users rarely justify learning a separate application but readily use capabilities available in familiar tools.
### Operational Efficiency Through Human-AI Collaboration
The Human-in-the-Loop pattern enables organizations to automate complex processes while maintaining human oversight at
critical decision points. AI agents handle routine analysis, data gathering, and draft generation, escalating to humans
only when judgment or approval is required. This collaboration model provides efficiency gains from automation while
preserving accountability and quality control through human checkpoints.
Organizations implement workflows like expense approval (AI reviews policies and flags issues, humans approve
exceptions), content moderation (AI identifies potential problems, humans make final calls), and customer inquiry
handling (AI drafts responses, humans review before sending) that combine automation benefits with human oversight.
### Reduced IT Overhead
Leveraging existing collaboration platform infrastructure eliminates requirements for deploying and supporting
additional applications. IT teams don't manage separate authentication systems, user provisioning, or help desk training
for AI access. Security and compliance controls already governing collaboration platforms - data loss prevention,
retention policies, audit logging - automatically apply to AI conversations without separate configuration.
This infrastructure reuse particularly benefits resource-constrained organizations where deploying and supporting
additional enterprise applications creates significant burden.
### Deployment Flexibility
The independent deployment model enables organizations to deploy bot capabilities selectively - enabling Teams
integration for headquarters while using Slack for regional offices, or deploying web chat widgets for customer-facing
scenarios while using Teams internally. Different channels can route to different agent configurations, supporting use
case segmentation or regulatory requirements where different regions require different AI handling.
## Implementation Approach
Built as a separate FastAPI-based service, the Bot Framework API integrates with Azure Bot Service through
platform-specific handlers managing channel differences. The service maintains conversation state in MongoDB with
configurable retention policies, while connecting to the platform's NATS event system for bidirectional agent
communication. Incoming bot activities translate to platform events for agent processing, with responses streamed back
and formatted appropriately for each channel. The stateless design with persisted conversation state enables horizontal
scaling based on conversation volume. Deployment as an independent Docker container supports flexible infrastructure
placement and independent version management from core platform services. Authentication leverages Azure AD for bot
registration while user identity flows from collaboration platforms through to platform permission systems, ensuring
consistent access control regardless of conversation channel.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/17_slack_teams_integrations/1_setup.md
title: "Azure Bot Service Integration"
description: ""
---
# Azure Bot Service Integration :speech_balloon: :100:
::: info **TL;DR - What is Azure Bot Service Integration?**
Azure Bot Service Integration transforms the Swiss AI Hub into a **multi-channel conversational platform** that connects
users to AI agents through familiar collaboration tools like Microsoft Teams and Slack. This integration provides
enterprise-grade bot connectivity with streaming responses, conversation persistence, and seamless human-in-the-loop
workflows, eliminating the need for users to switch between applications to access AI assistance.
:::
## What is Azure Bot Service Integration and How Does It Work? :brain:
Azure Bot Service Integration leverages **Microsoft Bot Framework** to provide unified conversational experiences across
multiple channels, making Swiss AI Hub's capabilities accessible wherever users naturally work and collaborate.
**Multi-Channel Bot Architecture** enables consistent AI interactions across:
- **Microsoft Teams** - Native integration with enterprise collaboration workflows
- **Slack** - Direct channel messaging for expert consultation and bot responses
- **Web Chat** - Browser-based interface for testing and development
- **Extensible Channel Support** - Any Bot Framework-supported platform
**[Bot-in-the-Loop Infrastructure](../../../3_sdk/6_feature_overview/bot-in-the-loop/)** powers sophisticated human-AI
collaboration by enabling AI agents to pause their workflows and seamlessly request human input through Slack channels.
When agents need expert knowledge or approval, they automatically post structured questions to designated channels,
capture responses, and continue processing with human-provided context.
**Intelligent Chat Bot Implementations** provide multiple completion strategies:
- **Agent Chat Bots** connect directly to Swiss AI Hub agents via NATS messaging for complex workflows
- **OpenAI Chat Bots** provide direct LLM integration for faster, simpler interactions
- **Streaming Support** delivers real-time response updates with typing indicators across all channels
**Enterprise-Grade Infrastructure** includes automated Azure AD app registration, secure credential management,
conversation persistence with configurable TTL, and comprehensive audit trails for compliance requirements.
**Key Technologies:**
- **Azure Bot Framework** - Multi-channel connectivity and message routing
- **Azure AD Integration** - Enterprise authentication and authorization
- **NATS Messaging** - Agent orchestration and event-driven workflows
- **MongoDB/Cosmos DB** - Conversation persistence and configuration storage
- **Infrastructure as Code** - Pulumi-based Azure resource deployment
## Why This is a Game-Changer for Your AI Strategy :trophy:
Azure Bot Service Integration eliminates the friction between AI capabilities and user adoption by meeting users where
they already work:
**🔄 Zero Context Switching**: Users access AI assistance directly within Microsoft Teams, Slack, and other familiar
collaboration platforms. No need to learn new interfaces or interrupt established workflows - AI becomes a natural part
of daily collaboration.
**🌐 Enterprise-Scale Multi-Channel Support**: Single Swiss AI Hub deployment serves users across multiple communication
platforms simultaneously. Whether teams use Teams, Slack, or other Bot Framework-supported channels, everyone gets
consistent AI assistance tailored to their preferred collaboration environment.
**⚡ Real-Time Streaming Responses**: Advanced streaming implementation provides immediate feedback with typing
indicators, partial responses, and incremental updates. Users see AI thinking and responding in real-time, creating
natural conversation flows that feel responsive and engaging.
**🛡️ Enterprise Security and Compliance**: Built on Azure AD authentication with comprehensive conversation audit
trails, configurable retention policies, and secure credential management. All bot interactions are logged and
traceable, meeting enterprise security and compliance requirements.
**🤝 Seamless Human-AI Collaboration**:
[Bot-in-the-Loop infrastructure](../../../3_sdk/6_feature_overview/bot-in-the-loop/) enables AI agents to naturally
escalate to human experts through structured Slack workflows. Complex decisions, approvals, and knowledge gaps are
handled smoothly without breaking user experience or losing conversation context.
::: details **Setting Up and Using Azure Bot Service Integration**
## Configuration Requirements
### Azure Infrastructure Setup
1. **Azure Bot Resource Creation**: Use the automated setup script
```bash
python packages/bot/setup_azure_bot.py \
--resource-group "your-resource-group" \
--bot-name "your-swiss-ai-hub-bot" \
--token-url "https://your-swiss-ai-hub-domain.com" \
--token-path "/api/v1/messages" \
--mongo-connection-string "mongodb://localhost:27017"
```
2. **Azure AD App Registration**: Automatically handled by setup script
- Creates Azure AD application with Bot Framework permissions
- Generates secure app credentials (App ID and password)
- Configures single-tenant or multi-tenant authentication
3. **Channel Configuration**: Manually configure in Azure Portal after setup
- **Microsoft Teams**: Add Teams channel in Azure Bot Service
- **Slack**: Create Slack App and link to Azure Bot Service
- **Web Chat**: Automatically configured for testing
### Local Development Setup
1. **Development Tunnel**: Expose local Swiss AI Hub for Azure Bot Service
```bash
# Install and configure Azure DevTunnel
devtunnel create --allow-anonymous
devtunnel port create -p 8000
devtunnel host
# Use tunnel URL in bot configuration
```
2. **Database Configuration**: Credentials stored in MongoDB
```json
{
"path": "/api/v1/messages",
"credentials": {
"APP_TYPE": "MultiTenant",
"APP_ID": "your-app-id",
"APP_PASSWORD": "your-app-password"
},
"system_message": "Custom bot instructions",
"slack_token": "slack-oauth-token"
}
```
## Usage Examples
### Microsoft Teams Integration
**Basic Chat Interaction:**
1. **Add Bot to Teams**: Install Swiss AI Hub bot in your Teams workspace
2. **Start Conversation**: Message the bot directly or mention in channels
3. **Streaming Responses**: See real-time typing indicators and incremental responses
4. **Conversation Persistence**: Context maintained across multiple interactions
**Advanced Features:**
- **Rich Responses**: Support for cards, buttons, and interactive elements
- **File Uploads**: Process documents and images directly in Teams
- **Thread Support**: Maintain conversation context in threaded discussions
### Slack Bot-in-the-Loop Workflows
For detailed information on Bot-in-the-Loop workflows, including expert consultation processes, channel configuration,
and agent integration patterns, see the dedicated
[Bot-in-the-Loop documentation](../../../3_sdk/6_feature_overview/bot-in-the-loop/).
### Multi-Bot Deployment
**Agent-Based Bots**: Connect to specific Swiss AI Hub agents
```python
# Configure bot to use specific agent
AgentChatBot(
agent_class="ExpertGroundedAgent",
agent_id="expert_agent_v1",
streaming=True
)
```
**OpenAI-Based Bots**: Direct LLM integration for simpler use cases
```python
# Configure bot with direct OpenAI access
OpenaiChatBot(
llm_config=OpenAIConfig(...),
system_message="You are a helpful assistant",
streaming=True
)
```
## Available Capabilities
**Bot Framework Integration:**
- **Multi-Channel Deployment**: Single codebase serves Teams, Slack, Web Chat, and other channels
- **Activity Processing**: Handle messages, typing events, conversation updates, and file uploads
- **Rich Message Support**: Cards, buttons, attachments, and interactive elements
- **Conversation Management**: Persistent state with configurable TTL (default 30 days)
**[Bot-in-the-Loop Infrastructure](../../../3_sdk/6_feature_overview/bot-in-the-loop/):**
- **Slack Channel Integration**: Direct posting to expert channels with thread support
- **Response Capture**: Automatic detection and processing of human responses
- **Conversation Threading**: Maintain context across multi-turn expert consultations
- **Knowledge Persistence**: Expert responses stored for organizational learning
**Chat Bot Implementations:**
- **Streaming Responses**: Real-time message updates with typing indicators
- **Agent Integration**: Direct connection to Swiss AI Hub agent workflows via NATS
- **OpenAI Integration**: Standalone LLM interactions for simple use cases
- **Error Handling**: Graceful degradation with user-friendly error messages
## Security and Best Practices
**Security Considerations:**
- **Azure AD Authentication**: Enterprise-grade authentication with role-based access
- **Secure Credential Storage**: Bot credentials encrypted and stored in MongoDB/Cosmos DB
- **Audit Trails**: Complete conversation history with user attribution and timestamps
- **Network Security**: Support for private endpoints and VNet integration
**Best Practices:**
- **Channel Organization**: Dedicate specific Slack channels for different expert domains
- **Conversation TTL**: Configure appropriate retention policies for compliance requirements
- **Bot Naming**: Use clear, descriptive names for multiple bot deployments
- **Monitoring**: Implement comprehensive logging and alerting for bot health
- **Capacity Planning**: Monitor conversation volume and response times
**Performance Optimization:**
- **Streaming Configuration**: Tune update frequency for optimal user experience
- **Conversation Cleanup**: Implement automated cleanup of expired conversations
- **Database Indexing**: Optimize MongoDB queries for conversation retrieval
- **Caching Strategy**: Cache frequently accessed configuration and credentials
:::
## Getting Started
To implement Azure Bot Service Integration in your Swiss AI Hub deployment:
1. **Run Azure Setup Script**: Use the provided automation to create Azure Bot resources and configure authentication
with your preferred database backend
2. **Configure Channels**: Set up Microsoft Teams and/or Slack integrations through Azure Portal channel configuration
3. **Deploy Bot Implementations**: Choose between agent-based bots for complex workflows or OpenAI-based bots for
simpler interactions, with streaming support for enhanced user experience
For detailed setup instructions, troubleshooting guidance, and advanced configuration options, refer to the
[Bot-in-the-Loop documentation](../../../3_sdk/6_feature_overview/bot-in-the-loop/) for human-AI collaboration
workflows, the [Expert Agents documentation](../../5_agents/9_expert_coordinator_agent/) for knowledge consultation
patterns, and the Swiss AI Hub Bot Developer's Guide for implementation details.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/17_slack_teams_integrations/2_bot_creation_guide.md
title: "Bot Creation Manual Setup Guide"
description: ""
---
# Bot Creation Manual Setup Guide :robot: :wrench:
::: info **TL;DR - What is This Guide?**
This comprehensive manual provides **step-by-step instructions for manually creating and configuring bots** with
Microsoft Teams and Slack integration. Use this guide when you need to create new bots from scratch, configure Azure Bot
Framework channels, or troubleshoot existing bot deployments. It covers everything from Teams Developer Portal setup to
MongoDB configuration and Slack OAuth integration.
:::
::: tip Automated Setup Available
Before proceeding with manual setup, consider using the **automated setup script** which handles most of these steps for
you:
```bash
python aihub_bot/setup_azure_bot.py \
--resource-group "my-resource-group" \
--bot-name "my-ai-hub-bot" \
--token-url "https://my-domain.com" \
--token-path "/api/v1/messages" \
--mongo-connection-string "mongodb://localhost:27017"
```
**When to use the automated script:**
- Creating new bots for production deployment
- Standard single-bot or multi-bot configurations
- Quick setup without customization
**When to use this manual guide:**
- Troubleshooting automated setup failures
- Understanding bot configuration in detail
- Creating custom or non-standard configurations
- Learning how bot integration works
For automated setup details, see the [Azure Bot Service Integration guide](../1_setup/).
:::
## Related Documentation :books:
- **[Slack & Teams Integrations Overview](../)** - High-level concepts and business value
- **[Azure Bot Service Integration](../1_setup/)** - Automated setup guide
- **[Swiss AI Hub Bot Developer's Guide](../../../6_code_deep_dive/packages/bot/)** - Technical implementation details
- **[Bot-in-the-Loop Documentation](../../../3_sdk/6_feature_overview/bot-in-the-loop/)** - Human-AI collaboration
workflows
## Key Terminology :book:
Understanding these terms is crucial for successful bot configuration:
| Term | Definition |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Bot Framework Messaging Endpoint** | The single public URL where Azure Bot Service sends ALL bot messages. Always `/api/v1/messages`. Configured in Teams Developer Portal (Step 3). |
| **MongoDB `path` Field** | Internal routing path that determines which bot implementation handles a conversation. Examples: `/api/v1/agent/chat/completions/...` or `/api/v1/openai/chat/completions`. Configured in MongoDB `bot_paths` collection (Step 7). |
| **App ID / Client ID** | Azure AD application identifier (UUID format). Same value used as Bot ID and in MongoDB `credentials.APP_ID`. |
| **Client Secret / App Password** | Azure AD application secret. Stored in MongoDB as `credentials.APP_PASSWORD`. Expires and must be rotated. |
| **Tenant ID** | Microsoft 365 tenant identifier. Required for SingleTenant bots, stored as `credentials.APP_TENANTID`. |
| **Bot-in-the-Loop** | Pattern where AI agents request human input via Slack channels during workflow execution. |
| **Slack Bot OAuth Token** | Token for Slack integration (format: `xoxb-...`). Stored in MongoDB as `slack_token`. |
::: warning Critical Distinction
**Bot Framework Messaging Endpoint** (`/api/v1/messages`) ≠ **MongoDB `path` Field** (e.g.,
`/api/v1/agent/chat/completions/...`)
These are two different concepts that serve different purposes. The messaging endpoint is where Azure Bot Service sends
messages. The `path` field determines how those messages are processed internally.
:::
## Prerequisites :clipboard:
Before starting, ensure you have access to:
- **Microsoft Teams Developer Portal** - For creating Teams apps and bots
- **MongoDB database** with `bot_paths` collection - For storing bot configuration
- **Azure Bot Framework** - For multi-channel bot management
- **Slack Workspace** with admin permissions - For Slack integration
______________________________________________________________________
## Part 1: Teams Developer Portal Setup :microsoft:
### Step 1: Create App with Basic Information
1. Navigate to [Teams Developer Portal](https://dev.teams.microsoft.com/)
2. Click **"Apps"** → **"New app"**
3. Fill in Basic Information:
- App name
- Short description
- Full description
- Developer information
- App URLs
- Application (client) ID (generate if needed)
### Step 2: Configure Permissions
1. Go to **"App features"** → **"Bot"**
2. Set required permissions:
- **Message Read** in Chat/Team
- **Message Send** in Chat/Team
3. Save permission changes
### Step 3: Create New Bot
1. In the app, navigate to **"Bot"** section
2. Click **"Set up"** or **"Create new bot"**
3. Enter the **Bot Framework messaging endpoint URL**:
- Format: `https://your-domain.com/api/v1/messages`
- This is the standard Azure Bot Service endpoint
- Must be publicly accessible from the internet
- For local development, use Azure DevTunnel or ngrok
4. **Write down the Bot ID** for later use
::: warning Bot Framework Endpoint vs MongoDB Path
**IMPORTANT DISTINCTION:**
- **Bot Framework Messaging Endpoint** (`/api/v1/messages`): The single entry point where Azure Bot Service sends ALL
bot messages. This is configured here in Teams Developer Portal.
- **MongoDB `path` Field** (e.g., `/api/v1/agent/chat/completions/LLMWrappingAgent/dev_agent/json`): Internal routing
path that determines which bot implementation handles the conversation. This is configured in MongoDB (Step 7) and
allows multiple bots to coexist.
**How it works:**
1. Azure Bot Service sends message to `/api/v1/messages`
2. Swiss AI Hub looks up the conversation's `path` in MongoDB `bot_paths` collection
3. Request is routed to the specific bot implementation
**Example:**
- Teams Developer Portal endpoint: `https://my-domain.com/api/v1/messages`
- MongoDB path for Agent Bot: `/api/v1/agent/chat/completions/LLMWrappingAgent/dev_agent/json`
- MongoDB path for OpenAI Bot: `/api/v1/openai/chat/completions`
These do NOT need to match! The messaging endpoint is always `/api/v1/messages`.
:::
::: tip Local Development
For local development, expose your bot server using Azure DevTunnel:
```bash
devtunnel create --allow-anonymous
devtunnel port create -p 8000
devtunnel host
# Use the https URL (e.g., https://abc123-8000.devtunnels.ms/api/v1/messages)
```
See the [Developer's Guide](../../../6_code_deep_dive/packages/bot/) for detailed local development setup.
:::
### Step 4: Create Client Secret
1. In the bot configuration, find **"Client secrets"**
2. Click **"Add a client secret"**
3. **IMPORTANT**: Copy and securely save the client secret immediately
- Secret format: `xxx~xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
- You won't be able to view it again
4. Note the secret's expiration date
::: danger Security Warning
Client secrets are only displayed once at creation time. Store them securely in a password manager or secrets vault
immediately. If lost, you'll need to generate a new secret and update your MongoDB configuration.
:::
### Step 5: Add Bot to App
1. Navigate back to app overview
2. Confirm bot is listed under **"App features"**
3. Verify bot ID matches the one created in Step 3
### Step 6: Publish App to Organization
1. Go to **"Publish"** → **"Publish to org"**
2. Review all configurations
3. Click **"Publish"**
4. Wait for admin approval (if required)
5. Once approved, note the **App/Client ID** and **Tenant ID**
______________________________________________________________________
## Part 2: MongoDB Configuration :floppy_disk:
### Step 7: Add Bot Path Entry
Add a new document to the `bot_paths` collection with the following structure:
```json
{
"path": "/api/v1/agent/chat/completions/LLMWrappingAgent/dev_agent/json",
"credentials": {
"APP_TYPE": "SingleTenant",
"APP_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"APP_PASSWORD": "xxx8Q~xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"APP_TENANTID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"system_message": "You are a helpful AI assistant.",
"slack_token": ""
}
```
**Required Fields:**
- `path`: The **internal routing path** for this specific bot implementation
- **This is NOT the Bot Framework messaging endpoint** (which is always `/api/v1/messages`)
- This determines which bot handler processes conversations
- Examples:
- `/api/v1/agent/chat/completions/LLMWrappingAgent/dev_agent/json` - Agent-based bot
- `/api/v1/openai/chat/completions` - Direct OpenAI bot
- `/api/v1/bitl/chat/completions` - Bot-in-the-Loop bot
- `credentials`: Object containing Azure Bot authentication
- `APP_TYPE`: Authentication type (`"SingleTenant"` or `"MultiTenant"`)
- `APP_ID`: Teams app client ID from Step 6
- `APP_PASSWORD`: Client secret from Step 4
- `APP_TENANTID`: Microsoft 365 tenant ID from Step 6 (required for SingleTenant)
- `system_message`: Default system message/instructions for the bot
- `slack_token`: Empty string initially (populated in Step 14 for Slack integration)
::: tip Multi-Bot Configuration
You can have **multiple bot implementations** with different `path` values, all sharing the same Bot Framework messaging
endpoint (`/api/v1/messages`):
**Agent-based bot for customer support:**
```json
{
"path": "/api/v1/agent/chat/completions/CustomerSupportAgent/prod/json",
"credentials": { "APP_ID": "xxx", "APP_PASSWORD": "xxx", "APP_TENANTID": "xxx", "APP_TYPE": "SingleTenant" },
"system_message": "You are a customer support assistant."
}
```
**OpenAI-based bot for general queries:**
```json
{
"path": "/api/v1/openai/chat/completions",
"credentials": { "APP_ID": "xxx", "APP_PASSWORD": "xxx", "APP_TENANTID": "xxx", "APP_TYPE": "SingleTenant" },
"system_message": "You are a helpful AI assistant."
}
```
Each conversation is associated with one `path`, which determines its bot implementation and behavior.
:::
::: tip Configuration Tip
Use descriptive path names that indicate the agent or functionality, making it easier to manage multiple bots. For
example, `/api/v1/agent/chat/completions/CustomerSupportAgent/production/json` clearly identifies the bot's purpose.
:::
______________________________________________________________________
## Part 3: Bot Framework & Slack Integration :slack:
### Step 8: Create Slack App
1. Navigate to [Slack API Apps](https://api.slack.com/apps)
2. Click **"Create New App"**
3. Choose **"From scratch"**
4. Give your app a name (e.g., "My Bot Name")
5. Select the workspace where you want to develop the app
6. Click **"Create App"**
### Step 8.5: Configure App Home Settings
1. Go to your Slack app's App Home settings:
- URL format: `https://api.slack.com/apps/{SLACK_APP_ID}/app-home`
- Replace `{SLACK_APP_ID}` with your Slack app ID
- Example: `https://api.slack.com/apps/A09QARZNF45/app-home`
2. Under **"Show Tabs"** section:
- Toggle **"Always show my bot as online"** to **ON**
- Toggle **"Home Tab"** to **ON**
3. Under **"Messages Tab"** section:
- **Leave "Messages Tab" disabled** - Bot will interact through channels and direct messages instead
- Uncheck "Allow users to send Slash commands and messages from the messages tab" (if visible)
4. Click **"Save Changes"** if prompted
::: info Messages Tab Configuration
The Messages Tab is typically disabled when using Bot Framework, as the bot communicates through channels, group chats,
and direct messages rather than the app's Messages tab.
:::
### Step 9: Configure Bot Framework Slack Channel
1. Navigate to [Bot Framework Portal](https://dev.botframework.com/)
2. Go to your bot's channels page:
- URL format: `https://dev.botframework.com/bots/channels?id={APP_ID}&channelId=slack`
- Replace `{APP_ID}` with your Teams app ID (from Step 6)
- Example: `https://dev.botframework.com/bots/channels?id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&channelId=slack`
3. Click **"Slack"** channel or **"Configure"** if already added
4. Copy the app credentials from your Slack app (from Step 8):
- **Client ID** (from Slack App Credentials)
- **Client Secret** (from Slack App Credentials)
5. Paste these into the Bot Framework Slack channel configuration
6. Copy the following URLs for later use:
- **Redirect URL** (needed for Step 10)
- **Event Subscription URL** (needed for Step 11)
7. Click **"Save"**
8. **IMPORTANT:** After saving, you'll be automatically redirected to Slack to install/reinstall the application
- This completes the OAuth flow
- Follow the prompts to authorize the app
- This may satisfy the event subscription requirements automatically
::: tip Automatic Configuration
The Bot Framework often configures many Slack settings automatically during the OAuth flow. After completing Step 9,
verify Steps 10-12 to confirm settings rather than manually configuring everything.
:::
### Step 10: Configure Slack OAuth
1. Go to your Slack app's OAuth settings:
- URL format: `https://api.slack.com/apps/{SLACK_APP_ID}/oauth`
- Replace `{SLACK_APP_ID}` with your Slack app ID
- Example: `https://api.slack.com/apps/A09QARZNF45/oauth`
2. Scroll down to **"Scopes"** section
3. Under **"Bot Token Scopes"**, click **"Add an OAuth Scope"**
4. Add the following scopes:
- `chat:write` - Allows the bot to send messages
- `assistant:write` - Allows the bot to interact with App Agents/Assistants
5. Under **"Redirect URLs"**, click **"Add New Redirect URL"**
6. Paste the **Redirect URL** from Bot Framework (Step 9)
7. Click **"Save URLs"**
::: info Automatic Scopes
Other required scopes (channels:history, groups:history, im:history, mpim:history) may be added automatically when you
subscribe to bot events in Step 12, or when you complete the Bot Framework OAuth flow.
:::
### Step 11: Configure Slack Event Subscriptions
1. Go to your Slack app's Event Subscriptions:
- URL format: `https://api.slack.com/apps/{SLACK_APP_ID}/event-subscriptions`
- Replace `{SLACK_APP_ID}` with your Slack app ID
- Example: `https://api.slack.com/apps/A09QARZNF45/event-subscriptions`
2. Toggle **"Enable Events"** to ON
3. In **"Request URL"**, paste the **Event Subscription URL** from Bot Framework (Step 9)
4. Wait for URL verification (should show "Verified ✓")
::: tip Already Configured?
If you were redirected to Slack during Step 9 and completed the installation, the event subscriptions may already be
configured automatically by Bot Framework. Verify this page to confirm.
:::
### Step 12: Subscribe to Bot Events (If Needed)
::: warning Optional Step
These event subscriptions may not be necessary if the Bot Framework Slack channel handles them automatically. Check if
events are already configured before adding manually.
:::
If events are not automatically configured, in the Event Subscriptions page, scroll down to **"Subscribe to bot
events"** and add the following:
| Event Name | Description | Required Scope |
| ---------------------------------- | ----------------------------------------------------------- | ------------------ |
| `message.channels` | A message was posted to a channel | `channels:history` |
| `message.groups` | A message was posted to a private channel | `groups:history` |
| `message.im` | A message was posted in a direct message channel | `im:history` |
| `message.mpim` | A message was posted in a multiparty direct message channel | `mpim:history` |
| `assistant_thread_started` | An App Agent thread was started | none |
| `assistant_thread_context_changed` | The context changed while an App Agent thread was visible | none |
::: info Automatic Scope Addition
When you add these events, Slack automatically adds the necessary OAuth scopes to your app configuration.
:::
Click **"Save Changes"**
### Step 13: Install Slack App to Workspace
::: tip May Already Be Complete
If you were redirected to Slack and completed the installation during Step 9, this step may already be complete. You can
verify by checking if the bot already appears in your Slack workspace.
:::
If not yet installed:
1. Go to your Slack app's installation page:
- URL format: `https://api.slack.com/apps/{SLACK_APP_ID}/install-on-team`
- Replace `{SLACK_APP_ID}` with your Slack app ID
- Example: `https://api.slack.com/apps/A09QARZNF45/install-on-team`
2. Click **"Install to Workspace"** (or **"Reinstall to Workspace"** if updating)
3. Review the permissions requested
4. Click **"Allow"**
5. **IMPORTANT:** Copy the **Bot User OAuth Token** that appears
- Format: `xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx`
- This is the token you'll add to MongoDB in the next step
**Alternative:** If already installed, retrieve your token from:
- **OAuth & Permissions** page: `https://api.slack.com/apps/{SLACK_APP_ID}/oauth`
- Look for **"Bot User OAuth Token"** under "OAuth Tokens for Your Workspace"
::: danger Token Security
The Slack Bot OAuth Token provides full access to your bot's capabilities. Store it securely and never commit it to
version control. Treat it with the same security as passwords and API keys.
:::
### Step 14: Add Slack OAuth Token to MongoDB
Update the bot path document in MongoDB to include the Slack OAuth token from Step 13:
```json
{
"_id": {
"$oid": "xxxxxxxxxxxxxxxxxxxxxxxx"
},
"path": "/api/v1/agent/chat/completions/LLMWrappingAgent/dev_agent/json",
"credentials": {
"APP_TYPE": "SingleTenant",
"APP_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"APP_PASSWORD": "xxx~xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"APP_TENANTID": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
},
"system_message": "You are a helpful AI assistant.",
"slack_token": "xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
}
```
**Slack OAuth Token Details:**
- Format: `xoxb-` followed by numbers and dashes
- Obtained from Step 13 during Slack app installation
- Replace the empty string `""` with the actual token
- Keep this token secure and never commit to version control
**To update existing document:**
```javascript
db.bot_paths.updateOne(
{ "path": "/api/v1/agent/chat/completions/LLMWrappingAgent/dev_agent/json" },
{ $set: { "slack_token": "xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx" } }
)
```
**Alternative: Update by \_id:**
```javascript
db.bot_paths.updateOne(
{ "_id": ObjectId("xxxxxxxxxxxxxxxxxxxxxxxx") },
{ $set: { "slack_token": "xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx" } }
)
```
______________________________________________________________________
## App Manifest Examples :page_facing_up:
These manifests show the complete configuration for both Slack and Teams apps. You can use these as reference or to
create apps programmatically.
### Slack App Manifest
```json
{
"display_information": {
"name": "LLM Wrapping Agent"
},
"features": {
"app_home": {
"home_tab_enabled": true,
"messages_tab_enabled": false,
"messages_tab_read_only_enabled": false
},
"bot_user": {
"display_name": "LLM Wrapping Agent",
"always_online": true
}
},
"oauth_config": {
"redirect_urls": [
"https://slack.botframework.com"
],
"scopes": {
"bot": [
"channels:history",
"groups:history",
"im:history",
"mpim:history",
"chat:write",
"assistant:write"
]
}
},
"settings": {
"event_subscriptions": {
"request_url": "https://slack.botframework.com/api/Events/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"bot_events": [
"assistant_thread_context_changed",
"assistant_thread_started",
"message.channels",
"message.groups",
"message.im",
"message.mpim"
]
},
"org_deploy_enabled": false,
"socket_mode_enabled": false,
"token_rotation_enabled": false
}
}
```
**Key Configuration Points:**
- **app_home**: Configuration for the app's home and messages tabs
- **home_tab_enabled**: Set to `true` to enable Home Tab
- **messages_tab_enabled**: Set to `false` (interaction happens through channels/DMs, not the Messages tab)
- **messages_tab_read_only_enabled**: Set to `false`
- **always_online**: Set to `true` to show bot as always online
- **redirect_urls**: Always `https://slack.botframework.com` for Bot Framework integration
- **request_url**: Format is `https://slack.botframework.com/api/Events/{APP_ID}` where `{APP_ID}` is your Teams app
client ID
- **bot scopes**: All 6 scopes are required for full functionality (including `chat:write` and `assistant:write`)
- **bot_events**: All 6 events enable the bot to receive messages across all conversation types
### Teams App Manifest
```json
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
"version": "1.0.0",
"manifestVersion": "1.23",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": {
"short": "LLM Agent",
"full": "LLM Wrapping Agent"
},
"developer": {
"name": "Your Organization Name",
"websiteUrl": "https://your-domain.com",
"privacyUrl": "https://your-domain.com/privacy",
"termsOfUseUrl": "https://your-domain.com/terms"
},
"description": {
"short": "LLMWrappingAgent",
"full": "LLMWrappingAgent"
},
"icons": {
"outline": "outline.png",
"color": "color.png"
},
"accentColor": "#ffffff",
"bots": [
{
"botId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scopes": [
"personal",
"team",
"groupChat"
],
"isNotificationOnly": false,
"supportsCalling": false,
"supportsVideo": false,
"supportsFiles": true
}
],
"validDomains": [],
"webApplicationInfo": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"authorization": {
"permissions": {
"resourceSpecific": [
{
"name": "ChannelMessage.Read.Group",
"type": "Application"
},
{
"name": "ChannelMessage.Send.Group",
"type": "Application"
},
{
"name": "ChatMessage.Read.Chat",
"type": "Application"
},
{
"name": "ChatMessage.Send.Chat",
"type": "Application"
}
]
}
}
}
```
**Key Configuration Points:**
- **id** and **webApplicationInfo.id**: Your Teams app client ID (APP_ID)
- **botId**: Same as your app client ID
- **scopes**: Enable bot in personal chats, teams, and group chats
- **supportsFiles**: Set to `true` to allow file uploads
- **Resource-Specific Permissions**:
- `ChannelMessage.Read.Group` - Read messages in channels
- `ChannelMessage.Send.Group` - Send messages in channels
- `ChatMessage.Read.Chat` - Read messages in chats
- `ChatMessage.Send.Chat` - Send messages in chats
### Using Manifests for App Creation
**Slack:**
1. Go to [Slack API Apps](https://api.slack.com/apps)
2. Click **"Create New App"** → **"From an app manifest"**
3. Select your workspace
4. Paste the Slack manifest JSON
5. Review and create
**Teams:**
1. Download the manifest as `manifest.json`
2. Add icon files (`outline.png` and `color.png`) to the same directory
3. Zip all three files together
4. In Teams Developer Portal, click **"Import app"**
5. Upload the zip file
______________________________________________________________________
## Verification Checklist :white_check_mark:
After completing all steps, verify:
**Teams Configuration:**
- [ ] Teams app is published and approved
- [ ] Bot Framework messaging endpoint is set to `/api/v1/messages`
- [ ] Bot Framework messaging endpoint URL is publicly accessible
- [ ] Bot responds to messages in Teams
- [ ] Bot permissions are correctly set (Message Read/Send in Chat/Team)
**MongoDB Configuration:**
- [ ] `bot_paths` entry exists with all required fields
- [ ] `credentials` object contains APP_TYPE, APP_ID, APP_PASSWORD, and APP_TENANTID (for SingleTenant)
- [ ] `path` field contains internal routing path (e.g., `/api/v1/agent/chat/completions/...`)
- [ ] `path` field is DIFFERENT from Bot Framework messaging endpoint (`/api/v1/messages`)
- [ ] `system_message` is configured appropriately for bot's purpose
- [ ] Client secret (APP_PASSWORD) is stored securely and hasn't expired
**Slack Configuration:**
- [ ] Slack app is created with correct name
- [ ] App Home configured: "Always show my bot as online" toggle ON
- [ ] App Home configured: "Home Tab" toggle ON
- [ ] App Home configured: "Messages Tab" is disabled (bot interacts through channels/DMs)
- [ ] Bot token scopes `chat:write` and `assistant:write` are added in Slack OAuth settings
- [ ] Bot Framework Slack channel is configured with Client ID and Client Secret
- [ ] Saved Bot Framework configuration and completed OAuth redirect to Slack
- [ ] Redirect URL is added to Slack OAuth settings
- [ ] Event subscription URL is verified (may be automatic)
- [ ] Bot events are subscribed (may be automatic via Bot Framework)
- [ ] Slack app is installed to workspace (may have happened during Step 9 redirect)
- [ ] `slack_token` is obtained from Slack OAuth & Permissions page
- [ ] `slack_token` is added to MongoDB
- [ ] Bot responds to messages in Slack channels
- [ ] Bot responds to direct messages in Slack
______________________________________________________________________
## Troubleshooting :wrench:
### Bot Not Responding in Teams
- **Messaging Endpoint Issues:**
- Verify Bot Framework messaging endpoint is set to `/api/v1/messages` in Teams Developer Portal
- Ensure the endpoint URL is publicly accessible (test with curl or browser)
- For local development, confirm Azure DevTunnel or ngrok is running
- **Authentication Issues:**
- Check `APP_PASSWORD` (client secret) is correct and hasn't expired
- Confirm `APP_TENANTID` and `APP_ID` match the values from Step 6
- Verify `APP_TYPE` is set correctly (`SingleTenant` or `MultiTenant`)
- **Configuration Issues:**
- Review app permissions in Teams Developer Portal (Message Read/Send in Chat/Team)
- Verify MongoDB `bot_paths` entry exists with correct credentials
- Check that `path` field in MongoDB contains a valid internal routing path
- Ensure conversation was created with the correct `path` value
### Slack Integration Issues
- Verify `slack_token` is valid (starts with `xoxb-`)
- Check Slack app has necessary bot token scopes:
- `chat:write` (required for sending messages)
- `assistant:write` (required for App Agent interactions)
- `channels:history`, `groups:history`, `im:history`, `mpim:history` (for message events)
- Verify App Home settings are configured:
- "Always show my bot as online" should be ON
- "Home Tab" should be ON
- "Allow users to send Slash commands and messages from the messages tab" should be checked
- Confirm bot is added to desired Slack channels (invite bot with @botname)
- Ensure all 6 bot events are subscribed in Event Subscriptions
- Verify Event Subscription URL shows "Verified ✓"
- Check Redirect URL is correctly added in Slack OAuth settings
- Ensure `slack_token` field is not an empty string in MongoDB
- Review Bot Framework Slack channel configuration
- Reinstall Slack app if scopes were changed after initial installation (required for scope changes to take effect)
### MongoDB Connection Issues
- Verify collection name is `bot_paths`
- Check document structure matches examples above
- Ensure all required fields are present in `credentials` object
- Validate `APP_ID` and `APP_TENANTID` are in correct UUID format
- Confirm `path` field starts with `/api/`
______________________________________________________________________
## Security Best Practices :shield:
1. **Never commit secrets to version control**
2. **Rotate client secrets before expiration**
3. **Use environment variables for sensitive data**
4. **Restrict MongoDB access with proper authentication**
5. **Monitor OAuth token usage for anomalies**
6. **Keep audit logs of bot path modifications**
7. **Use HTTPS for all messaging endpoints**
______________________________________________________________________
## Support :sos:
For issues or questions:
- **Teams Developer Portal**:
[Microsoft Teams Documentation](https://learn.microsoft.com/en-us/microsoftteams/platform/)
- **Bot Framework**: [Azure Bot Service Documentation](https://learn.microsoft.com/en-us/azure/bot-service/)
- **Slack API**: [Slack API Documentation](https://api.slack.com/)
______________________________________________________________________
## Next Steps :rocket:
After completing the manual bot setup:
1. **Test Your Bot**: Send a message in Teams or Slack to verify the bot responds correctly
2. **Review Logs**: Check application logs for any errors or warnings during bot interactions
3. **Configure Additional Features**: Explore [Bot-in-the-Loop](../../../3_sdk/6_feature_overview/bot-in-the-loop/) for
human-AI collaboration
4. **Implement Custom Logic**: See the [Developer's Guide](../../../6_code_deep_dive/packages/bot/) for custom bot
implementations
5. **Monitor Performance**: Set up observability and monitoring for production deployments
______________________________________________________________________
*Last Updated: November 14, 2025*
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/18_api.md
title: "API"
description: ""
---
# OpenAI-Compatible REST API
## Concept and Purpose
The OpenAI-Compatible REST API provides a standards-based HTTP interface built on FastAPI that mirrors the OpenAI API
specification exactly. This design enables organizations to migrate existing AI-powered applications to the Swiss AI Hub
platform without modifying application code - only the API endpoint URL and authentication token need to change.
The strategic value of this compatibility layer lies in reducing migration friction and protecting existing technology
investments. Organizations can adopt Swiss AI Hub infrastructure for data sovereignty, cost control, or compliance
reasons while preserving their application ecosystem built on OpenAI SDKs and libraries.
## Core Design Principles
### Seamless Migration and Integration
The API implements complete compatibility with OpenAI's interface, supporting all major capabilities including
conversational AI (chat completions), semantic search (embeddings), image generation, and audio processing
(speech-to-text and text-to-speech). Applications developed using OpenAI's Python or JavaScript SDKs function without
modification, enabling rapid platform adoption and reducing implementation risk.
This compatibility extends to both request and response formats, streaming behaviors, and error handling patterns.
Organizations can validate the platform using existing test suites and migration scripts, accelerating evaluation and
deployment timelines.
### Vendor-Neutral Model Access
The API abstracts underlying model providers, supporting multiple LLM sources including Swiss LLM Cloud, self-hosted
vLLM models, and other OpenAI-compatible services. This vendor neutrality provides several business advantages:
organizations can switch between model providers transparently without application changes, implement cost optimization
strategies by routing requests to different vendors based on workload characteristics, maintain data sovereignty through
self-hosted model options, and leverage hybrid deployment models combining cloud and on-premise resources.
Model selection and routing occur transparently at the platform level, allowing centralized governance and optimization
without requiring coordination across application teams.
### Extended Model Concept: Swiss AI Hub Assistants
Beyond standard language models, the Swiss AI Hub extends the OpenAI model concept to include platform-native AI
assistants (agents). These assistants appear as specialized models alongside traditional LLMs, enabling applications to
interact with complex, stateful agent workflows using the same familiar chat interface.
This extension provides a migration path for organizations seeking to evolve from simple LLM interactions toward
orchestrated agent workflows. Applications can start by calling basic language models and progressively adopt more
sophisticated agents without architectural changes - the same API interface serves both use cases.
## Supported Capabilities
The API provides full-spectrum AI functionality compatible with modern LLM applications:
**Conversational AI**: Complete chat completion support with both synchronous and streaming response modes, enabling
interactive applications and progressive UI updates. The interface supports multi-turn conversations, function calling,
and multimodal inputs (text and images) for vision-capable models.
**Semantic Search**: Embedding generation converts text into vector representations for semantic search, similarity
matching, and retrieval-augmented generation workflows. This capability supports batch processing and multiple embedding
model configurations.
**Multimodal Generation**: Image generation from text prompts and audio processing capabilities including speech-to-text
transcription (supporting multiple audio formats and languages) and text-to-speech synthesis with configurable voices
and streaming output.
**Model Discovery**: Dynamic model listing enables applications to discover available LLM models and Swiss AI Hub
assistants at runtime, supporting adaptive interfaces and centralized model governance.
## Business Value
### Reduced Migration Risk and Cost
Organizations can adopt the Swiss AI Hub platform without rewriting applications, eliminating migration project costs
and reducing adoption risk. Existing development teams continue using familiar OpenAI SDKs and patterns, avoiding
retraining overhead. This compatibility preserves investments in application code, testing infrastructure, and
operational runbooks.
### Centralized Governance and Cost Control
The compatibility layer provides a single control point for model access across the organization. Platform
administrators can implement cost controls, usage quotas, and routing policies without requiring changes to individual
applications. Model provider switching occurs transparently, enabling cost optimization and avoiding vendor lock-in.
### Progressive Enhancement Path
The unified interface between basic LLM models and sophisticated AI assistants enables organizations to evolve their AI
capabilities incrementally. Applications built for simple model access can progressively adopt more advanced agent-based
workflows as organizational maturity increases, without requiring architectural redesign.
## Implementation Approach
Built on FastAPI, the API operates as part of the main platform service with stateless request handling enabling
horizontal scaling. Authentication integrates with organizational identity providers via OAuth2, and hierarchical
permissions control access to both LLM models and AI assistants. Request routing logic transparently directs model
requests to the LLM proxy layer while converting assistant interactions into platform events for agent processing,
maintaining clean separation between external model access and internal agent orchestration.
## Key Responsibilities
**Authentication and Authorization**: The API layer enforces security boundaries, validating user identities through
integration with organizational identity providers (OAuth2, OIDC) and enforcing role-based access control policies.
**Request Routing**: Incoming requests are validated, authenticated, and routed to appropriate backend services. The API
acts as a facade, abstracting the complexity of the distributed service architecture from clients.
**Protocol Translation**: The API translates between external protocols (HTTP/REST, WebSocket) and internal event-driven
communication patterns, bridging synchronous client expectations with asynchronous backend processing.
**Session Management**: For conversational interfaces, the API maintains session context, managing long-lived
connections and ensuring state consistency across multiple interactions.
## API Types and Interfaces
The Swiss AI Hub platform exposes multiple API types, each optimized for specific interaction patterns and use cases:
### 1. OpenAI-Compatible REST API
A standards-based HTTP API providing full compatibility with OpenAI's API specification, enabling seamless migration for
applications built on OpenAI SDKs. The API supports chat completions, embeddings, image generation, and audio processing
(speech-to-text and text-to-speech) with identical endpoint structures and request/response formats. This serves as a
drop-in replacement for OpenAI endpoints, allowing existing applications to leverage Swiss AI Hub infrastructure without
code changes. The API supports both direct LLM model access and Swiss AI Hub assistants (agents), with both streaming
and non-streaming modes for all capabilities.
### 2. Agent Interaction REST API
A native HTTP API designed specifically for managing and interacting with AI agents, threads (conversations), processes,
and platform resources. This API provides comprehensive access to the platform's full capabilities, including agent
discovery and configuration, conversation lifecycle management, process execution and monitoring, event history access,
and user/role administration. It is optimized for building native Swiss AI Hub applications that leverage the platform's
complete agent orchestration and process automation features.
### 3. WebSocket API
A bidirectional, real-time communication channel enabling live event streaming and continuous updates for interactive
applications. The WebSocket API delivers agent events, status updates, and streaming responses as they occur with low
latency, supporting progressive display of agent responses in user interfaces. It provides connection-based session
management with token-based authentication and automatic disconnection on authorization failure. This API is optimized
for real-time user interfaces requiring immediate feedback and live updates during agent execution.
### 4. Model Context Protocol (MCP) Server
An HTTP-based MCP server that exposes Swiss AI Hub API endpoints as resources and tools accessible to AI development
assistants and automation tools. This enables AI coding assistants like Claude Code and Gemini CLI to query platform
state, inspect agents, and interact with the Swiss AI Hub API during development sessions. The server provides automatic
resource and tool discovery from API endpoints, read-only access to GET endpoints as MCP resources, and dynamic schema
generation from OpenAPI specifications.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/18_api/1_openai_compatible_api.md
title: "OpenAI-Compatible REST API"
description: ""
---
# OpenAI-Compatible REST API
## Concept and Purpose
The OpenAI-Compatible REST API provides a standards-based HTTP interface built on FastAPI that mirrors the OpenAI API
specification exactly. This design enables organizations to migrate existing AI-powered applications to the Swiss AI Hub
platform without modifying application code - only the API endpoint URL and authentication token need to change.
The strategic value of this compatibility layer lies in reducing migration friction and protecting existing technology
investments. Organizations can adopt Swiss AI Hub infrastructure for data sovereignty, cost control, or compliance
reasons while preserving their application ecosystem built on OpenAI SDKs and libraries.
## Core Design Principles
### Seamless Migration and Integration
The API implements complete compatibility with OpenAI's interface, supporting all major capabilities including
conversational AI (chat completions), semantic search (embeddings), image generation, and audio processing
(speech-to-text and text-to-speech). Applications developed using OpenAI's Python or JavaScript SDKs function without
modification, enabling rapid platform adoption and reducing implementation risk.
This compatibility extends to both request and response formats, streaming behaviors, and error handling patterns.
Organizations can validate the platform using existing test suites and migration scripts, accelerating evaluation and
deployment timelines.
### Vendor-Neutral Model Access
The API abstracts underlying model providers, supporting multiple LLM sources including Swiss LLM Cloud, self-hosted
vLLM models, and other OpenAI-compatible services. This vendor neutrality provides several business advantages:
organizations can switch between model providers transparently without application changes, implement cost optimization
strategies by routing requests to different vendors based on workload characteristics, maintain data sovereignty through
self-hosted model options, and leverage hybrid deployment models combining cloud and on-premise resources.
Model selection and routing occur transparently at the platform level, allowing centralized governance and optimization
without requiring coordination across application teams.
### Extended Model Concept: Swiss AI Hub Assistants
Beyond standard language models, the Swiss AI Hub extends the OpenAI model concept to include platform-native AI
assistants (agents). These assistants appear as specialized models alongside traditional LLMs, enabling applications to
interact with complex, stateful agent workflows using the same familiar chat interface.
This extension provides a migration path for organizations seeking to evolve from simple LLM interactions toward
orchestrated agent workflows. Applications can start by calling basic language models and progressively adopt more
sophisticated agents without architectural changes - the same API interface serves both use cases.
## Supported Capabilities
The API provides full-spectrum AI functionality compatible with modern LLM applications:
**Conversational AI**: Complete chat completion support with both synchronous and streaming response modes, enabling
interactive applications and progressive UI updates. The interface supports multi-turn conversations, function calling,
and multimodal inputs (text and images) for vision-capable models.
**Semantic Search**: Embedding generation converts text into vector representations for semantic search, similarity
matching, and retrieval-augmented generation workflows. This capability supports batch processing and multiple embedding
model configurations.
**Multimodal Generation**: Image generation from text prompts and audio processing capabilities including speech-to-text
transcription (supporting multiple audio formats and languages) and text-to-speech synthesis with configurable voices
and streaming output.
**Model Discovery**: Dynamic model listing enables applications to discover available LLM models and Swiss AI Hub
assistants at runtime, supporting adaptive interfaces and centralized model governance.
## Business Value
### Reduced Migration Risk and Cost
Organizations can adopt the Swiss AI Hub platform without rewriting applications, eliminating migration project costs
and reducing adoption risk. Existing development teams continue using familiar OpenAI SDKs and patterns, avoiding
retraining overhead. This compatibility preserves investments in application code, testing infrastructure, and
operational runbooks.
### Centralized Governance and Cost Control
The compatibility layer provides a single control point for model access across the organization. Platform
administrators can implement cost controls, usage quotas, and routing policies without requiring changes to individual
applications. Model provider switching occurs transparently, enabling cost optimization and avoiding vendor lock-in.
### Progressive Enhancement Path
The unified interface between basic LLM models and sophisticated AI assistants enables organizations to evolve their AI
capabilities incrementally. Applications built for simple model access can progressively adopt more advanced agent-based
workflows as organizational maturity increases, without requiring architectural redesign.
## Implementation Approach
Built on FastAPI, the API operates as part of the main platform service with stateless request handling enabling
horizontal scaling. Authentication integrates with organizational identity providers via OAuth2, and hierarchical
permissions control access to both LLM models and AI assistants. Request routing logic transparently directs model
requests to the LLM proxy layer while converting assistant interactions into platform events for agent processing,
maintaining clean separation between external model access and internal agent orchestration.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/18_api/2_agent_interaction_api.md
title: "Agent Interaction REST API"
description: ""
---
# Agent Interaction REST API
## Concept and Purpose
The Agent Interaction REST API, built on FastAPI, serves as the platform-native HTTP interface for accessing the full
capabilities of the Swiss AI Hub. While the OpenAI-Compatible API provides standardized LLM access for migration
scenarios, this API exposes the complete agent orchestration, process automation, and platform management functionality
unique to the Swiss AI Hub.
This API is designed for organizations building native applications that leverage the platform's advanced capabilities:
multi-agent collaboration, long-running business processes, comprehensive observability, and sophisticated knowledge
management. It provides programmatic control over the entire platform lifecycle, from agent discovery and configuration
through process execution and quality evaluation.
## Core Design Principles
### Platform-Native Capabilities
The API provides direct access to capabilities that distinguish the Swiss AI Hub from simple LLM proxies: stateful
conversations involving multiple specialized agents, orchestrated business processes that coordinate AI with human
decision points, comprehensive event history for audit and debugging, and centralized knowledge management for
retrieval-augmented generation. These capabilities enable organizations to build sophisticated AI-powered workflows
rather than simple question-answer interactions.
Applications can discover available agents dynamically, configure multi-agent teams for specific tasks, initiate complex
business processes, and monitor execution through detailed event streams. This flexibility supports both interactive
applications requiring immediate responses and batch processes running autonomously over extended periods.
### Event-Driven Integration
The API serves as an HTTP gateway to the platform's event-driven core, translating synchronous HTTP requests into
asynchronous platform events. This architecture provides several advantages: requests return immediately while agents
process tasks in the background, distributed agent services scale independently without API changes, comprehensive event
streams enable real-time monitoring and historical analysis, and operations remain observable and debuggable through
structured event logs.
This design bridges traditional request-response expectations from web and mobile applications with the asynchronous,
distributed nature of autonomous agent operations. Applications receive immediate acknowledgment of requests while the
platform orchestrates complex, potentially long-running agent workflows. .
## Business Value
### Comprehensive Platform Control
Unlike simple LLM APIs that provide basic model access, this interface exposes the full platform for organizations
building sophisticated AI solutions. Development teams gain programmatic control over agent configuration, process
orchestration, and knowledge management without requiring direct infrastructure access. This enables application-level
automation while maintaining security boundaries and audit trails.
### Operational Visibility and Compliance
The extensive observability capabilities address critical enterprise requirements for transparency and compliance.
Organizations can demonstrate to auditors exactly how AI systems reached specific decisions, reconstruct conversations
for dispute resolution, identify performance degradation before it impacts users, and monitor costs by tracking agent
execution and resource utilization across teams and projects.
### Scalable Multi-Agent Architectures
The API's support for multi-agent collaboration enables organizations to build scalable AI solutions by composing
specialized agents. Rather than training single, monolithic models for diverse tasks, organizations can develop focused
agents for specific domains and orchestrate them through this interface. This modular approach reduces individual agent
complexity, enables independent agent improvement cycles, and supports reuse of agents across different business
processes.
## Implementation Approach
Built on FastAPI, the API operates as part of the main platform service with stateless design enabling horizontal
scaling. Authentication integrates with organizational identity providers via OAuth2, and hierarchical permissions
control resource access at runtime. Request handling translates HTTP operations into NATS events that flow through the
platform's event system, maintaining clean separation between the synchronous HTTP interface and asynchronous agent
execution. All operations are instrumented via OpenTelemetry for distributed tracing, and structured logging captures
contextual information for comprehensive observability across HTTP and event boundaries.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/18_api/3_websocket_api.md
title: "WebSocket API"
description: ""
---
# WebSocket API
## Concept and Purpose
The WebSocket API, built on FastAPI, provides bidirectional, real-time communication channels for applications requiring
immediate feedback during AI operations. Unlike traditional request-response HTTP patterns where clients must repeatedly
poll for updates, WebSocket connections enable the platform to push events to clients instantly as they occur,
supporting modern interactive user experiences.
This capability addresses a fundamental challenge in AI applications: users need to observe what autonomous agents are
doing in real-time to build trust and maintain engagement. Without real-time visibility, long-running agent operations
appear as black boxes, creating uncertainty and reducing user confidence in AI-powered systems.
## Core Design Principles
### Real-Time Transparency and Trust
Modern AI applications require responsive interfaces that provide continuous feedback during agent execution. Users
expect to observe agent reasoning steps as they occur, receive progressive streaming of response content, monitor
real-time status changes, and receive immediate notification of issues or completion. This transparency transforms
opaque AI operations into observable, understandable processes.
The business impact of real-time visibility extends beyond user experience: it builds trust in AI systems by
demonstrating how agents reach conclusions, enables users to interrupt or redirect long-running operations before
wasting time, reduces perceived latency through progressive disclosure of results, and provides immediate feedback when
issues occur rather than silent failures.
### Event-Driven Architecture Bridge
The WebSocket API serves as a bridge between the platform's internal event-driven architecture and external client
applications. All platform operations - agent execution, process orchestration, message handling - generate structured
events that flow through the NATS messaging backbone. The WebSocket layer translates these internal events into
client-consumable messages, maintaining a consistent view of platform state across all connected applications.
This architecture ensures that multiple clients observing the same operations receive identical event streams,
supporting collaborative scenarios where teams work together with shared AI assistants. The event-sourced foundation
also enables reliable delivery: even if connections temporarily fail, clients can recover lost events through the event
history APIs.
### Security and Access Control
Despite providing real-time access to platform operations, the WebSocket API maintains strict security boundaries.
Connections are read-only from the client perspective - applications receive events but cannot publish through the
WebSocket channel. This design ensures that all actions requiring authorization validation flow through REST APIs where
proper security checks occur.
Event filtering based on user permissions ensures clients only receive events for resources they can access:
conversations they participate in, agents they can use, and processes they own or contribute to. This fine-grained
access control supports multi-tenant deployments where different users share platform infrastructure while maintaining
complete data isolation.
## Supported Capabilities
The API delivers real-time visibility across three primary operation types:
**Agent Execution Monitoring**: Applications receive continuous updates as agents execute tasks - reasoning steps, tool
invocations, response generation, and completion status. Streaming response chunks enable progressive display of agent
output, similar to typing indicators in messaging applications. This visibility helps users understand agent
capabilities and limitations, building appropriate trust levels for different task types.
**Conversation Updates**: Real-time notification of conversation state changes ensures users stay synchronized with
collaborative discussions. Applications learn immediately when new messages arrive, participants join or leave
conversations, or conversation metadata changes. This supports both human-human and human-AI collaboration patterns
where multiple parties contribute to problem-solving.
**Process State Tracking**: Complex multi-step business processes generate events as they progress through workflow
stages. Applications can display process status, highlight current steps, indicate completion progress, and notify users
when their input is required. This visibility enables proactive engagement rather than reactive notification - users see
processes advancing and can prepare responses before being explicitly prompted.
## Business Value
### Enhanced User Experience and Engagement
Real-time feedback transforms how users interact with AI systems. Rather than submitting requests and waiting with no
indication of progress, users observe continuous activity that maintains engagement and builds confidence. This
transparency is particularly valuable for complex agent operations that may take minutes or hours - users can monitor
progress, understand what the agent is currently doing, and make informed decisions about whether to wait or pursue
alternative approaches. Progressive response streaming in chat interfaces feels more natural and engaging than long
pauses followed by complete responses, and process status visibility helps users understand where they are in multi-step
workflows.
### Operational Efficiency and Cost Management
Real-time monitoring enables users to identify and abort unproductive operations early, avoiding wasted compute
resources and API costs. When agents pursue incorrect reasoning paths or encounter issues, immediate visibility allows
intervention before significant resources are consumed. This capability becomes increasingly important as organizations
scale AI deployments across multiple teams and use cases.
Administrators benefit from real-time platform monitoring - observing agent utilization patterns, identifying
performance bottlenecks, and receiving immediate notification of system issues. This operational visibility supports
proactive management rather than reactive troubleshooting.
### Collaborative AI Workflows
The WebSocket API's multi-client support enables team collaboration scenarios where multiple users work together with
shared AI assistants. All participants receive identical event streams, ensuring everyone observes the same agent
behaviors and conversation developments. This capability supports use cases like group decision-making with AI
assistance, training scenarios where experts guide AI interactions, and quality review processes where supervisors
monitor agent performance.
## Implementation Approach
Built on FastAPI's WebSocket capabilities, the API integrates directly with the platform's NATS JetStream event
backbone. Persistent connections handle thousands of concurrent clients per instance with minimal resource overhead.
Authentication occurs via bearer tokens validated against organizational identity providers, with automatic connection
termination for authentication failures. Event filtering applies hierarchical permission checks before delivery,
ensuring users receive only events for authorized resources. The architecture scales horizontally across API instances
using NATS-based event broadcasting, maintaining consistent event delivery regardless of which instance serves a
particular client connection. Typical event delivery latency remains below 50 milliseconds, supporting truly real-time
user experiences.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/18_api/4_dynamic_endpoints.md
title: "Dynamic Agent and Process Endpoints"
description: ""
---
# Dynamic Agent and Process Endpoints :rocket: :100:
::: info **TL;DR - What are Dynamic Endpoints?**
The Swiss AI Hub automatically creates REST API endpoints on-the-fly based on the agents and processes available in your
system. When a new agent or process comes online, the API gateway instantly generates custom endpoints tailored to that
specific component's capabilities, eliminating the need for manual API development and ensuring your system stays
perfectly synchronized.
:::
## What are Dynamic Endpoints and How Do They Work? :brain:
Dynamic Agent and Process Endpoints represent a revolutionary approach to API generation in distributed AI systems.
Instead of pre-defining static API routes, the Swiss AI Hub employs **intelligent endpoint discovery services** that:
- **Continuously scan** the NATS message bus for available agents and processes
- **Automatically generate** REST endpoints based on each component's event specifications
- **Dynamically register** these endpoints with the FastAPI application at runtime
- **Maintain synchronization** between your running services and available API endpoints
This system leverages two core discovery services:
- **`AgentEndpointsDiscoveryService`**: Creates endpoints like `/agents/{agent_class}/{agent_id}/{event_name}` for
sending events to specific agents
- **`ProcessEndpointsDiscoveryService`**: Generates endpoints like `/processes/{process_class}/{process_id}/{route}` for
human and program interaction with processes
The discovery process uses **NATS-based service discovery** to query running components about their capabilities, then
uses the **Jambo SchemaConverter** to transform event schemas into Pydantic models that power fully-typed, documented
API endpoints.
## Why This is a Game-Changer for Your AI Strategy :trophy:
This feature eliminates one of the biggest barriers to scaling AI systems - **API development bottlenecks**:
**🔗 Zero-Configuration API Generation**: New agents and processes automatically expose their capabilities through REST
APIs without any manual development. Deploy a new agent, and its endpoints appear instantly in your API documentation.
**🧠 Type-Safe Dynamic Integration**: Every dynamic endpoint is fully typed using Pydantic models generated from event
schemas, providing automatic validation, serialization, and comprehensive OpenAPI documentation.
**🛡️ Enterprise-Grade Security**: Dynamic endpoints inherit all security features including role-based access control,
authentication, and fine-grained permissions, ensuring new capabilities are secure by default.
**⚡ Real-Time System Adaptation**: Your API automatically adapts to system changes - when agents scale up or down, their
endpoints appear or disappear accordingly, keeping your integration layer perfectly synchronized.
**🌐 Seamless External Integration**: External systems and webhooks can interact with any agent or process through
predictable REST endpoints, enabling powerful integrations with third-party services and business systems.
::: details **Setting Up and Using Dynamic Endpoints**
## Configuration Requirements
Dynamic endpoints are enabled automatically when you start the Swiss AI Hub with the full infrastructure stack:
1. **NATS Message Bus**: Required for service discovery communication
2. **API Gateway**: The `aihub_api` service with `lifetime_manager` enabled
3. **Discovery Services**: Automatically started by the lifetime manager
## Agent Endpoint Generation
For each discovered agent, the system creates endpoints following this pattern:
```
POST /api/v1/{tenant_id}/agents/{agent_class}/{agent_id}/{event_name}
```
**Example**: An agent with class `rag_agent` and ID `customer_support` that accepts `UserMessageEvent` would get:
```
POST /api/v1/{tenant_id}/agents/rag_agent/customer_support/user_message_event
```
## Process Endpoint Generation
For each discovered process, the system creates endpoints based on the process definition:
```
GET /api/v1/{tenant_id}/processes/{process_class}/{process_id}/{route} # Get form
POST /api/v1/{tenant_id}/processes/{process_class}/{process_id}/{route} # Submit form
GET /api/v1/{tenant_id}/processes/{process_class}/{process_id}/{walkthrough_id}/{route} # Continue process
POST /api/v1/{tenant_id}/processes/{process_class}/{process_id}/{walkthrough_id}/{route} # Submit continuation
```
## Available Capabilities
Dynamic endpoints provide:
- **Full OpenAPI Documentation**: Every endpoint appears in `/docs` with complete request/response schemas
- **Type Validation**: Automatic request validation based on event specifications
- **Authentication**: Inherited from the base controller security model
- **Error Handling**: Standardized error responses and HTTP status codes
- **Caching**: Built-in response caching for discovery-related operations
## Security and Best Practices
**Access Control**:
- Agent endpoints require `aihub.user.agent.{agent_class}.{agent_id}` permission
- Process endpoints require `aihub.user.process.{process_class}.{process_id}` permission
**Rate Limiting**: Discovery services run on configurable intervals (default: 60 seconds) to balance responsiveness with
system load
**Monitoring**: All dynamic endpoint usage is logged and traced through standard Swiss AI Hub observability tools
**Performance**: Dynamic endpoints are cached and only regenerated when the system topology changes
:::
## Getting Started
To begin using dynamic endpoints in your Swiss AI Hub:
1. **Start the Infrastructure**: Ensure NATS and the API gateway are running with the standard Docker Compose setup
2. **Deploy Agents/Processes**: Any agent or process that connects to NATS will automatically get endpoints
3. **Discover Endpoints**: Check `/api/v1/active/docs` to see all dynamically generated endpoints
4. **Integrate**: Use the standard REST endpoints to integrate with external systems or build custom frontends
The dynamic endpoint system transforms the Swiss AI Hub from a static API into a living, adaptive integration layer that
grows with your AI capabilities.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/18_api/5_api_tokens.md
title: "API Tokens"
description: "Generate a personal API token to authenticate REST API requests."
---
# API Tokens
Calling the REST API from a script, integration, or command line requires a bearer token. Rather than reusing the
short-lived JWT from your browser session, you create a long-lived personal token (prefixed `sk-`) that authenticates as
your own user. This page walks through generating one entirely from the browser using the built-in Swagger UI.
There is no dedicated screen for this in the admin interface yet, so the Swagger UI at `/api/v1/docs` is the supported
way to create a token interactively.
## Get a session token
The token endpoints are themselves protected, so you first need to authenticate the request that creates your `sk-`
token. The simplest credential is the access token your browser already holds after you log in.
1. Log in to the platform in your browser.
2. Open the browser developer tools, switch to the Network tab, and click any request to the API.
3. Under the request headers, find `Authorization: Bearer eyJ...` and copy the value after `Bearer `.
::: warning Copy the access token, not the id token
The API validates the `account` audience, which only the access token carries. If you copy the id token (for example
from the `oidc.user:...` entry in local storage) the request fails with `401 ... Invalid token`. The Network tab always
shows the access token, so it is the most reliable source.
To check a token, decode its payload and confirm `"typ": "Bearer"` and an `"aud"` that includes `"account"`.
:::
## Create the token in Swagger
Open the Swagger UI for the deployment:
```
https:///api/v1/docs
```
1. Click Authorize (top right).
2. Paste the access token into the HTTPBearer field. Enter the token only, without a `Bearer ` prefix.
3. Click Authorize, then Close.
4. Find `POST /api/v1/{tenant_id}/tokens/` (Create API Token) and click Try it out.
5. Set `tenant_id` to `active`, which resolves to your active tenant.
6. Provide a request body. The name is 1 to 100 characters and the expiry date must be a future ISO-8601 datetime:
```json
{
"name": "my-api-token",
"expiry_date": "2026-12-31T23:59:59Z"
}
```
7. Click Execute.
A `201` response contains the token:
```json
{
"id": "...",
"name": "my-api-token",
"expiry_date": "2026-12-31T23:59:59Z",
"token": "sk-..."
}
```
::: danger Copy the token immediately
The `token` value is returned only once, at creation. It is never shown again. If you lose it, revoke the token and
create a new one.
:::
## Use the token
Send the `sk-` token as a bearer credential on every API request:
```bash
curl https:///api/v1/active/agents/ \
-H "Authorization: Bearer sk-..."
```
Unlike the browser session token, the `sk-` token stays valid until its expiry date, so it suits scripts and long-lived
integrations.
## List and revoke tokens
The same controller exposes the rest of the token lifecycle, also callable from the Swagger UI:
| Operation | Endpoint |
| --------- | ---------------------------------------------- |
| Create | `POST /api/v1/{tenant_id}/tokens/` |
| List | `GET /api/v1/{tenant_id}/tokens/` |
| Revoke | `DELETE /api/v1/{tenant_id}/tokens/{token_id}` |
The list endpoint returns each token's id, name, and expiry date, but never the token value. Revoking a token deletes it
permanently and immediately invalidates any client still using it.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/19_mcp.md
title: "Model Context Protocol (MCP)"
description: ""
---
# Model Context Protocol (MCP) Server
## Concept and Purpose
The Model Context Protocol (MCP) Server exposes Swiss AI Hub capabilities to AI development assistants and automation
tools through a standardized protocol. Built on FastAPI and integrated directly into the main API service, this
interface enables AI assistants to interact with the platform.
## Core Design Principles
### Standards-Based Integration
MCP is an emerging standard for exposing application functionality to AI assistants in structured, discoverable ways. By
implementing MCP rather than proprietary interfaces, the Swiss AI Hub ensures compatibility with any MCP-compatible
tool, enables automatic integration as new AI development tools adopt the protocol, and provides type safety through
schema-based interactions that prevent incorrect tool usage.
The standards-based approach future-proofs the platform's development ecosystem: as new AI assistants and automation
tools emerge, they gain immediate access to Swiss AI Hub capabilities without requiring custom integration work.
### Automatic API Translation
The MCP Server automatically translates the existing FastAPI REST interface into MCP resources, eliminating duplicate
implementation and maintenance burden. OpenAPI specifications generated from code annotations transform into MCP schemas
automatically, ensuring consistency between human-facing REST APIs and AI-facing MCP resources. Changes to platform
capabilities instantly reflect in both interfaces without separate documentation or translation steps.
This architecture maintains a single source of truth: FastAPI route definitions, type annotations, and documentation
strings serve both development communities simultaneously.
## Supported Capabilities
The MCP Server provides AI assistants with read-only access to platform information across four domains:
**Agent Discovery and Inspection**: AI assistants can query available agents, retrieve detailed agent configurations and
capabilities, examine agent execution patterns and performance characteristics, and understand which agents handle which
task types. This enables assistants to recommend appropriate agents for specific problems and generate correct agent
invocation code.
**Conversation Analysis**: Access to conversation threads, message histories, and participant information helps AI
assistants understand application context. Assistants can trace conversation flows, analyze multi-agent collaboration
patterns, and provide debugging guidance based on actual conversation structures rather than assumptions.
**Observability and Diagnostics**: Complete access to event streams, execution logs, and time-series analytics enables
AI-assisted debugging. Assistants can correlate events across components, identify performance bottlenecks, trace errors
to root causes, and suggest optimizations based on actual operational data.
**Process Monitoring**: Visibility into business process definitions, execution states, and completion histories allows
AI assistants to understand application workflows. This supports process optimization, error analysis, and guidance on
implementing new process variants.
## Business Value
### AI-Assisted Operations and Monitoring
AI assistants can query live platform state for operational insights and troubleshooting. Operations teams receive
immediate answers about process execution status, agent performance metrics, event histories, and system health without
manually navigating interfaces or parsing logs. This reduces mean time to resolution for incidents and enables proactive
issue identification through AI-powered anomaly detection across conversation patterns, agent behaviors, and business
process execution.
### Intelligent Knowledge Management
The MCP interface provides AI assistants with access to knowledge bases, document repositories, and RAG indices,
enabling sophisticated knowledge discovery and analysis. Users can ask natural language questions that retrieve and
synthesize information across distributed document collections, identify knowledge gaps, and receive recommendations for
content improvements. This capability is valuable for compliance teams needing to locate specific regulatory references
and researchers exploring large technical document collections.
### Enhanced Development Productivity
Developers benefit from AI assistants with direct platform access for code generation and debugging. Code suggestions
validate against current API schemas rather than generic patterns, debugging conversations include actual platform
state, and test generation uses real agent configurations. Organizations report development productivity improvements of
30-50% when AI assistants have structured system access. New team members gain productivity faster through immediate,
context-aware guidance that reduces onboarding time and eliminates dependency on documentation searches.
### Process Analysis and Optimization
AI assistants can analyze business process definitions, execution histories, and performance patterns to identify
optimization opportunities. By querying process instances, agent interactions, and completion metrics, assistants
provide actionable insights for workflow improvements, bottleneck identification, and resource allocation. This
capability supports continuous process improvement initiatives and helps organizations maximize return on AI automation
investments.
## Implementation Approach
Built using the FastMCP library, the MCP server generates resources automatically from FastAPI route definitions and
OpenAPI specifications. The server mounts at `/mcp` on the main API service, sharing authentication infrastructure,
database connections, and event system access with REST endpoints. Only read-only operations (GET endpoints) are
exposed, maintaining a secure development interface that allows platform observation without state modification.
Authentication uses the same OAuth2/SAML/LDAP identity providers as REST APIs, with hierarchical permission checks
filtering resources based on user access rights. AI development tools configure MCP connections via `.mcp.json` files in
project repositories, enabling automatic platform access during development sessions. The architecture scales
horizontally with API instances, requires no separate deployment, and adds minimal resource overhead to the existing
service.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/20_security.md
title: "Security"
description: ""
---
# Security
This section covers security features and best practices in the Swiss AI Hub platform architecture.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/20_security/1_authentication.md
title: "Authentication and Authorization"
description: ""
---
# Authentication and Authorization
The Swiss AI Hub implements authentication and authorization based on industry-standard OpenID Connect (OIDC) and OAuth
2.0 protocols. This standards-based approach ensures compatibility with enterprise identity providers while maintaining
secure access control across all platform resources.
## Authentication: OpenID Connect (OIDC)
The platform authenticates users through OpenID Connect, an identity layer built on top of OAuth 2.0. This enables
secure user authentication through enterprise identity providers such as Microsoft Entra ID (Azure Active Directory)
while supporting the OAuth 2.0 Authorization Code Flow.
### How Authentication Works
**Token-Based Authentication:** Users authenticate through their organization's identity provider, which issues a JSON
Web Token (JWT) containing cryptographically signed claims about the user's identity. The platform validates these
tokens on every request to ensure authenticity and freshness.
**JWT Token Validation:** The platform retrieves public keys from the identity provider's JWKS (JSON Web Key Set)
endpoint and uses them to verify the cryptographic signature of each JWT token. This validation includes checking the
token's issuer, audience, expiration time, and signature integrity according to the JWT standard (RFC 7519).
**User Identity Resolution:** After successful token validation, the platform extracts the user's unique identifier
(OID) and basic profile information (name, email) from the JWT token claims. Role assignments are managed locally within
the platform through tenant-scoped role entities, not fetched from the identity provider.
### Supported Authentication Methods
**OAuth 2.0 Authorization Code Flow:** The primary authentication method for interactive users follows the OAuth 2.0
Authorization Code Flow with PKCE (Proof Key for Code Exchange). Users are redirected to their organization's identity
provider for authentication, and upon successful login, receive a secure authorization code that is exchanged for access
tokens.
**Bearer Token Authentication:** For API access and programmatic integrations, the platform supports standard OAuth 2.0
Bearer Token authentication. API clients present valid JWT tokens in the HTTP Authorization header, which are validated
using the same JWKS-based verification process.
## Authorization: Permission-Based Access Control
Authorization is implemented independently from authentication, enabling consistent access control regardless of how
users authenticate. The platform evaluates permissions for every API request based on the user's assigned roles and the
hierarchical permission model described in the [Permissions](../../11_access_management/2_permissions/).
### Enterprise Identity Provider Integration
The platform integrates with enterprise identity providers through standard OIDC/OAuth 2.0 protocols. Any OIDC-compliant
provider (Microsoft Entra ID, Google Workspace, Okta, Auth0, Keycloak) can be used for authentication.
**Generic OIDC Integration:** The platform connects to the configured OIDC provider as an OAuth 2.0 authorization server
and identity provider. User authentication is delegated to the provider, which handles credential validation,
multi-factor authentication, and session management according to the organization's security policies.
**Local Role Management:** User profiles are extracted from JWT token claims (name, email, OID). Roles are managed
locally within the platform through tenant-scoped role assignments, not synced from the identity provider. This
decouples platform authorization from any specific identity provider's group or role model.
### How Authorization Works
Authorization decisions are made independently from authentication. After a user's identity is established through OIDC
authentication, the platform determines what resources and operations the user can access based on their assigned roles.
**Permission Evaluation Process:**
1. The platform resolves the user's role assignments from the local tenant-scoped role database
2. Each role is associated with a set of access rules stored in the platform database
3. For every API request, the platform evaluates the required permission against the user's access rules
4. Access rules support hierarchical matching with wildcard patterns for flexible permission management
5. The authorization decision (grant or deny) is made and logged for audit purposes
**API-Level Permission Enforcement:** Every API endpoint declares its required permissions. These permissions are
automatically checked before the endpoint logic executes, ensuring no resource access bypasses authorization. The
permission evaluation uses the hierarchical permission model described in the
[Permissions](../../11_access_management/2_permissions/).
**Dynamic Authorization:** For operations requiring runtime permission checks, the platform provides programmatic access
to the permission evaluation system. This enables filtering result sets based on user permissions, implementing
different behaviors for different access levels, and validating permissions before resource-intensive operations.
## Dynamic Identity Provider Discovery
The login page dynamically discovers available identity providers from Keycloak at runtime. When a user visits the login
page, the frontend calls `GET /api/v1/{tenant_id}/auth-providers/` — an unauthenticated API endpoint that queries the
Keycloak Admin API using a dedicated, least-privilege service account (`aihub-api-service`) with only the
`view-identity-providers` permission.
The API filters the provider list to only include enabled, visible providers and returns their alias, display name, and
icon. Results are cached for 5 minutes. The frontend renders a branded login button for each provider. Clicking a button
initiates the OIDC Authorization Code Flow with `kc_idp_hint` set to the provider's alias, redirecting the user directly
to the upstream identity provider without showing Keycloak's login theme.
This approach eliminates any frontend configuration for identity providers — adding or removing an IdP is a
Keycloak-only change.
### Configuring Provider Icons
Each identity provider can have a custom icon displayed on its login button. Icons are configured directly in the
Keycloak identity provider's `config` map as the `icon` field using PrimeIcon CSS classes (e.g., `pi-microsoft`,
`pi-google`). Providers without an icon configured fall back to `pi-sign-in`.
To set an icon, add the `icon` field to the identity provider's configuration in
`infra/deployment/templates/configs/keycloak/bootstrap/identity-providers.json.j2`:
```json
"config": {
"clientId": "...",
"icon": "pi-microsoft"
}
```
### Direct Keycloak Login
When `KEYCLOAK_SHOW_KEYCLOAK_LOGIN=true` (API environment variable, default: `true`), an additional "Login with
Keycloak" button appears alongside federated provider buttons. This enables username/password login through Keycloak's
own user store — useful for development environments or deployments where some users authenticate directly with Keycloak
rather than through an external IdP.
## Admin Service Authentication via OAuth2 Proxy
Internal admin services (Dagster, Attu, SeaweedFS) are protected by [OAuth2 Proxy](https://oauth2-proxy.github.io/)
instances that sit in front of each service. OAuth2 Proxy handles the full OIDC login flow against Keycloak before
forwarding authenticated requests to the upstream service. Only users with the `AIHubSysAdmin` role can access these
services.
Due to the split-horizon networking in Docker deployments (containers use internal hostnames, browsers use external
URLs), OIDC discovery is skipped and endpoints are configured explicitly.
## Langfuse Access
Langfuse does not sit behind an OAuth2 Proxy — it uses its native Keycloak SSO integration (OIDC client `langfuse`).
Access is restricted to users with the `AIHubSysAdmin` role directly in Keycloak: the `langfuse` client carries the
marker client scope `langfuse-sysadmin-gate`, which activates a conditional deny in the authentication flows
(`browser-aihub` browser flow and the post-broker-login flow). Users without `AIHubSysAdmin` are denied at the Keycloak
login — on fresh logins via the identity provider as well as on existing SSO sessions.
## Hardening: Keycloak Admin Console Access
The Keycloak admin console (`https://auth./admin/`) is protected by username and password but is accessible from
any IP address by default. For production deployments, restricting access to the admin console and metrics endpoint to
known administrator IP addresses is strongly recommended.
### Recommended: IP Allowlisting via Traefik
The platform uses Traefik v3 as its reverse proxy. Traefik's
[`ipAllowList`](https://doc.traefik.io/traefik/middlewares/http/ipallowlist/) middleware can restrict access to the
Keycloak admin paths while keeping the OIDC login endpoints publicly accessible for all users.
**Implementation steps:**
1. Add an environment variable to `.env` with your allowed IP ranges:
```bash
KEYCLOAK_ADMIN_ALLOWED_IPS="203.0.113.0/24,198.51.100.10/32"
```
2. In `docker-compose.yml`, add a second Traefik router for admin paths on the `keycloak` service labels (alongside the
existing `keycloak` router):
```yaml
# Admin-only router with IP restriction (higher priority than public router)
- "traefik.http.routers.keycloak-admin.rule=Host(`auth.${DOMAIN}`) && (PathPrefix(`/admin`) || PathPrefix(`/metrics`))"
- "traefik.http.routers.keycloak-admin.entrypoints=websecure"
- "traefik.http.routers.keycloak-admin.tls=true"
- "traefik.http.routers.keycloak-admin.priority=7500"
- "traefik.http.routers.keycloak-admin.middlewares=keycloak-admin-ipallowlist,keycloak-security-headers"
- "traefik.http.routers.keycloak-admin.service=keycloak"
# IP allowlist middleware
- "traefik.http.middlewares.keycloak-admin-ipallowlist.ipallowlist.sourcerange=${KEYCLOAK_ADMIN_ALLOWED_IPS}"
```
The public router (priority 7000) continues to serve OIDC endpoints (`/realms/...`) without restriction, while the admin
router (priority 7500) intercepts `/admin` and `/metrics` requests and rejects connections from non-allowlisted IPs with
a `403 Forbidden` response.
::: tip
The same pattern can be applied to any service exposed through Traefik. Consider also restricting access to the Traefik
dashboard itself if it is enabled in production.
:::
## Keycloak Realm Roles and Automatic Assignment
Keycloak manages realm-level roles that determine whether a user may access the platform. These roles are coarse access
gates — fine-grained permissions are managed locally by the platform through tenant-scoped roles (see
[Permissions](../../11_access_management/2_permissions/)).
Two realm roles take effect in the platform:
| Role | Effect |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AIHubAccess` | Required for platform login. Users without this role are denied at the Keycloak login flow. |
| `AIHubSysAdmin` | Platform administrator. Read from the token to grant admin access and gate the OAuth2-Proxy admin tools (Dagster, Attu, SeaweedFS, Backup) and Langfuse. |
::: info Realm roles vs. platform roles
The `aihub` realm defines only these two roles. Fine-grained, day-to-day permissions are handled separately by
**tenant-scoped roles** managed inside the platform — these may share names such as `AIHubUser` or `AIHubAdmin` but are
unrelated to Keycloak realm roles and are not derived from the IdP. Assign only `AIHubAccess` and `AIHubSysAdmin` in
your identity provider.
For the operator setup of the Azure app registration and role assignment, see
[Identity Provider Setup](../../3_deployment_guide/10_identity_provider_setup/).
:::
By default, no realm roles are automatically assigned to new users: a federated user receives only the realm roles
explicitly mapped from their IdP claims (principle of least privilege). At the Keycloak / IdP level there are only ever
**two** roles to assign — `AIHubAccess` and `AIHubSysAdmin` (see the info box above). `AIHubUser` / `AIHubAdmin` and the
other "platform roles" are **not** Keycloak realm roles and are never set on the IdP; they are tenant-scoped and
configured inside the platform (see [Default platform roles](#default-platform-roles) below).
### Mapping the realm roles from the IdP
The `azure-ad` provider already maps the two realm roles from the Azure `roles` claim via two `oidc-role-idp-mapper`
entries in `infra/deployment/templates/configs/keycloak/bootstrap/identity-providers.json.j2` (`role-mapper-access` →
`AIHubAccess`, `role-mapper-sysadmin` → `AIHubSysAdmin`). To federate the same two roles from a different provider, add
the equivalent mappers — targeting only `AIHubAccess` and `AIHubSysAdmin`:
```json
{
"name": "role-mapper-access",
"identityProviderAlias": "",
"identityProviderMapper": "oidc-role-idp-mapper",
"config": {
"syncMode": "INHERIT",
"claim": "roles",
"claim.value": "AIHubAccess",
"role": "AIHubAccess"
}
}
```
::: warning Identity providers are bootstrap config
Identity providers and their mappers are seeded from `bootstrap/identity-providers.json.j2` by the realm import on the
**first start only** — they are not reconciled by keycloak-config-cli. Edits made in the Keycloak admin console survive
restarts, and changes to the config file reach an already-initialized deployment only via the admin console (or a fresh
realm database).
:::
::: warning AIHubAccess is required to log in
`AIHubAccess` is enforced at the Keycloak login flow ("Post Broker Login - AIHubAccess Check"): a user without it is
denied regardless of any other assignment. Make sure your IdP grants `AIHubAccess` to everyone who should be able to log
in.
:::
### Default platform roles
Day-to-day permissions come from **platform roles** (`AIHubUser`, `AIHubAdmin`, `AIHubAgentUser`, …), which are
tenant-scoped and managed inside the platform — not in Keycloak and not from the IdP. Which roles a new user receives is
controlled by the `AIHUB_USER_SIGNUP_*` environment variables (`AIHUB_USER_SIGNUP_DEFAULT_ROLES`,
`AIHUB_USER_SIGNUP_REGULAR_USER_ROLES`, `AIHUB_USER_SIGNUP_FIRST_ADMIN_USER_ROLES`), and each tenant is seeded with its
default role set at creation. See [Access Management](../../11_access_management/1_authentication_setup/) and
[Permissions](../../11_access_management/2_permissions/).
## Security Standards and Operational Capabilities
### Standards Compliance
The authentication and authorization implementation adheres to industry-standard protocols and specifications:
**OIDC and OAuth 2.0 Standards:**
- OpenID Connect Core 1.0 for authentication
- OAuth 2.0 Authorization Framework (RFC 6749)
- OAuth 2.0 Authorization Code Flow with PKCE
- JSON Web Token (JWT) - RFC 7519
- JSON Web Key Set (JWKS) - RFC 7517
- OAuth 2.0 Bearer Token Usage (RFC 6750)
**Cryptographic Security:** All JWT tokens are validated using RSA-256 cryptographic signatures. Public keys are
retrieved from the identity provider's JWKS endpoint and cached for performance. Token validation includes signature
verification, issuer validation, audience validation, and expiration checking on every request.
### Audit and Monitoring
All authentication and authorization events are comprehensively logged with structured metadata for audit and security
monitoring purposes. This includes user identity, requested resources, permission evaluations, access decisions, and
full request context.
**Security Event Logging:** The platform integrates with OpenTelemetry standards to provide structured, traceable
security events. This enables correlation of security events across distributed system components and supports
compliance requirements for audit trails.
**Real-Time Security Monitoring:** Security teams can monitor authentication patterns, authorization failures, token
validation events, and access patterns in real-time. This visibility enables rapid detection and response to potential
security incidents.
### Regulatory and Enterprise Compliance
The authentication and authorization architecture supports compliance with regulatory requirements and enterprise
security standards:
**Data Protection Compliance:**
- GDPR-compliant user authentication and data handling
- Swiss data protection law compliance through self-hosted deployment options
- Comprehensive audit trails meeting regulatory requirements for access logging
- Data sovereignty maintained through on-premises or Swiss cloud deployment
**Enterprise Security Requirements:**
- Multi-factor authentication support through enterprise identity providers
- Integration with existing enterprise identity infrastructure
- Protection against common authentication attacks (token replay, session hijacking, CSRF)
- Secure token lifecycle management with expiration and revocation
- HTTPS-only communication for all authentication flows
**Security Best Practices:**
- Zero-trust security model with authentication required for all API access
- Separation of authentication and authorization concerns
- Principle of least privilege through granular permission system
- Defense in depth with multiple layers of security controls
- Regular token validation and refresh mechanisms
This standards-based approach to authentication and authorization ensures the platform meets enterprise security
requirements while remaining interoperable with standard identity providers and security infrastructure. The use of OIDC
and OAuth 2.0 provides proven security mechanisms that are widely understood, audited, and trusted in enterprise
environments.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/20_security/2_input_validation.md
title: "Input Validation"
description: ""
---
# Input Validation
The Swiss AI Hub implements input validation to protect against common attack vectors including path traversal, MIME
type confusion, and malformed data.
## File upload validation
### File type whitelist
The platform restricts uploads to approximately 40 approved file extensions across multiple categories: document formats
(PDF, Office, text, markdown), image formats (JPEG, PNG, TIFF, WebP), audio formats (WAV, MP3), and structured data
(JSON, XML).
### MIME type validation
The platform validates that the provided content type matches the expected MIME type for the file extension. This
prevents MIME type confusion attacks where malicious files disguise themselves with incorrect MIME types.
### Filename validation
Filenames must start with alphanumeric characters and are validated to block:
- Path traversal attempts (`..`, `/`, `\`, null bytes)
- Extension spoofing (maximum 3 dot-separated parts, 10-character extension limit)
### File size validation
Files must be larger than 0 bytes. Empty files are rejected. Maximum size limits are enforced at the application or
reverse proxy level.
## Namespace and database name validation
Database and namespace names follow similar validation rules to prevent path traversal in logical storage paths.
## What input validation protects
- Path traversal attacks
- MIME type confusion
- Extension spoofing
- Null byte injection
- Executable file uploads
- Resource exhaustion (via size limits)
## Related documentation
- [Authentication & Authorization](../1_authentication/) - User identity and access control
- [Container Security](../3_container_security/) - Isolated file processing
- [RBAC](../../11_access_management/2_permissions/) - Permission-based upload restrictions
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/20_security/3_container_security.md
title: "Container Security"
description: ""
---
# Container Security
The Swiss AI Hub uses containerization (Docker) for all services with basic security hardening implemented.
## Implementation status
| Security Control | Status |
| ------------------------- | -------------- |
| Non-Root User Execution | Implemented |
| Multi-Stage Builds | Implemented |
| Minimal Base Images | Implemented |
| Seccomp Profiles | Not configured |
| AppArmor/SELinux | Not configured |
| Capability Dropping | Not configured |
| Read-Only Root Filesystem | Not configured |
| Network Segmentation | Implemented |
## Implemented controls
### Non-root user execution
Every container runs as a non-privileged user (UID 1000, GID 1000). All application processes run without root
privileges, limiting damage from container escape vulnerabilities and preventing privilege escalation.
### Multi-stage builds
Containers use multi-stage builds separating build and runtime environments. The builder stage compiles dependencies
with build tools, while the runtime stage copies only necessary artifacts, excluding build tools from the final image.
This reduces attack surface and image size.
### Minimal base images
Base images use the slim variant (~150MB) instead of full Debian (~1GB). This provides fewer packages, smaller attack
surface, and reduced CVE exposure while maintaining compatibility with Python packages.
### Regular base image updates
Container images are rebuilt from source for each release, ensuring base images stay current with security patches.
Images follow immutable infrastructure principles and are never patched in place.
## Code-execution sandbox
User code runs in `open-terminal`, the one container that executes arbitrary code. It is isolated in three ways.
- **Network.** It sits alone in the `code-sandbox` network, shared only with the services that call it (`open-webui`,
`universal-agent` and `api`). A breakout can reach those callers, but has no direct network path to the rest of the
application tier or to the data tier, and outside development the network has no outbound internet access (see
[Network Isolation](../../2_architecture/4_network_isolation/)).
- **Per-user homes.** Each user works in their own home directory. The platform ships a patched sandbox image
(`open-terminal-office`) whose file API resolves links and only reaches the requesting user's own home. Without the
patch, a link inside one home could read or overwrite another user's files, or reach the server's environment. The
image build fails if the patch no longer matches upstream, so a base-image bump cannot drop it silently.
- **Mirror and downloads.** The backup mirror mounts the homes read-only, skips symbolic links, and uses an S3 identity
limited to its own bucket. Files served back to the browser through **My Files** show inline only as PDF, raster image
or plain text. Everything else, HTML and SVG included, is sent as a download with `Content-Security-Policy: sandbox`,
because agents write these files.
Shell commands still run as the user and see the sandbox system under normal permissions. Isolation between users rests
on the file API confinement and the container boundary, not on a separate container per user.
## Related documentation
- [Deployment Options](../../3_deployment_guide/1_deployment_options/) - Container orchestration
- [Input Validation](../2_input_validation/) - Preventing malicious input
- [Data Encryption](../5_data_encryption/) - Data protection
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/20_security/4_network_security.md
title: "Network security"
description: ""
---
# Network security
The Swiss AI Hub uses defense-in-depth network security. Multiple independent layers protect the platform, its data, and
its users.
All internal services (Swiss AI Hub API, Web UI, LiteLLM Proxy, databases) run in isolated Docker containers on private
networks. The Traefik reverse proxy is the only component accessible from the internet, accepting public traffic on
ports 80 and 443.
Traefik routes requests to the correct internal service. Backend services remain isolated and never get exposed directly
to the public internet.
```
Internet
↓
[Firewall/NSG]
↓ (ports 80, 443)
[VM Public IP]
↓
[Traefik Reverse Proxy]
↓
[Docker Internal Network]
├── Swiss AI Hub API
├── Web UI
├── LiteLLM Proxy
├── Database Services
└── Background Workers
↓
(Outbound to External Services)
├── LLM Providers (Swiss LLM Cloud, or local vLLM for GPU deployments)
├── Authentication (Microsoft Entra ID, Azure AD)
├── Jina AI (Web Search & Embeddings)
└── Customer APIs (SharePoint, Confluence, Custom REST APIs)
```
## Security layers
Security applies at every stage of a request, from network edge to application logic.
### Network firewall (NSG)
The Network Security Group (NSG) or firewall enforces a default deny policy. Only ports 80 (HTTP) and 443 (HTTPS) are
accessible from the public internet. All other ports are blocked. You can restrict administrative access like SSH to
specific trusted IP ranges.
### Reverse proxy (Traefik)
Traefik serves as the single entry point and secures all incoming connections. It terminates TLS (requiring HTTPS with
TLS 1.2+), automatically provisions and renews certificates via Let's Encrypt, and injects security headers like HSTS
and X-Frame-Options. Rate limiting protects backend services from brute-force and simple DoS attacks.
### Authentication (IAM)
Azure AD OAuth2 handles user authentication, integrating with corporate identity. This enables Role-Based Access Control
(RBAC) for fine-grained permissions. API keys authenticate service-to-service communication. Session management with
configurable timeouts protects user sessions.
### Container isolation
Application services run as non-root users in isolated Docker containers with minimal privileges. Container networking
rules prevent direct communication between unrelated services. Resource limits mitigate resource exhaustion attacks.
Images get updated regularly with security patches.
### Network segmentation
The platform uses five isolated Docker networks (`proxy`, `backend`, `data`, `storage`, `egress`) to enforce the
principle of least privilege at the network layer. Services are assigned only to the networks they require:
- **Internal networks** (`backend`, `data`, `storage`) have no external internet access
- **Egress network** allows outbound internet access only, with Inter-Container Communication (ICC) disabled
- Services needing to browse external websites (e.g., playwright) use the `egress` network without exposing ingress
See [Network Isolation](../../2_architecture/4_network_isolation/) for detailed network topology and service
assignments.
### Data protection
Presidio automatically detects and anonymizes Personally Identifiable Information (PII) in LLM requests. AI-powered
sensitive information guards scan responses before delivery to users. An audit trail logs all data access and
processing.
## Related documentation
- [Network isolation](../../2_architecture/4_network_isolation/) - Docker network zones and service assignments
- [Network requirements](../../3_deployment_guide/7_network_requirements/) - Firewall rules and connectivity
- [Deployment options](../../3_deployment_guide/1_deployment_options/) - Architecture and hosting strategies
- [Container security](../3_container_security/) - Container isolation and hardening
- [Authentication](../1_authentication/) - Authentication mechanisms
- [Input validation](../2_input_validation/) - Input sanitization and validation
- [Infrastructure layers](../../../1_vision_and_positioning/4_infrastructure_layers/) - Infrastructure component
overview
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/20_security/5_data_encryption.md
title: "Data Encryption"
description: ""
---
# Data Encryption at Rest
> **️⚠️Implementation Status**: This encryption approach is not yet implemented. This section describes the planned
> security concept.
## LUKS Volume Encryption
All platform data is stored within Docker volumes, which are encrypted using Linux Unified Key Setup (LUKS). This
approach provides full-disk encryption for all persistent data stored by the platform, including:
- Application databases
- Vector store indices
- Document storage and ingestion artifacts
- Configuration data and secrets
- Logs and observability data
### Security Properties
LUKS encryption provides:
- **AES-256 encryption** in XTS mode for the entire volume
- **Key management** independent of the data, allowing key rotation without re-encrypting the entire volume
- **Protection against physical access**: Data remains encrypted when the system is powered off or volumes are detached
- **Transparent operation**: Applications interact with encrypted volumes without modification; encryption/decryption
occurs at the block device layer
### Threat Mitigation
This encryption-at-rest strategy protects against:
- Unauthorized physical access to storage media
- Volume snapshot exfiltration
- Disk theft or improper disposal
- Backup media compromise
The encryption does **not** protect against threats while the system is running and volumes are mounted, such as
memory-based attacks or compromised application credentials. These threats are addressed through complementary controls
in access management, network segmentation, and runtime security monitoring.
# Data Encryption in Transit
All data transmitted between the platform and external clients, as well as connections to external services, is
encrypted using industry-standard Transport Layer Security (TLS) protocols.
## Edge Encryption
The platform employs **Traefik** as the reverse proxy and ingress controller, providing TLS termination at the network
edge. All external connections are secured through:
- **TLS 1.2 and TLS 1.3** support for client connections
- **Automatic HTTP to HTTPS redirection** ensuring all traffic uses encrypted channels
- **Let's Encrypt integration** for automated certificate provisioning and renewal in production environments
- **Custom certificate support** for environments with existing PKI infrastructure
### Production Certificate Management
In production deployments, TLS certificates are managed through:
- **ACME protocol** with Let's Encrypt for automatic certificate issuance and renewal
- **Certificate validation** via HTTP-01 challenge mechanism
- **Automated rotation** before expiration, preventing service interruptions
### Security Headers
Traefik applies security-hardened HTTP headers to all responses:
- **Strict-Transport-Security (HSTS)**: Enforces HTTPS for one year, including all subdomains
- **Content-Security-Policy**: Restricts frame embedding to same-origin
- **X-Content-Type-Options**: Prevents MIME type sniffing
- **Referrer-Policy**: Limits referrer information leakage
## External Service Connections
All connections from the platform to external services utilize encrypted transport:
- **Swiss LLM Cloud**: HTTPS connections to the configured Swiss LLM Cloud endpoints
- **OAuth/OIDC Providers**: TLS-secured authentication flows
- **LLM Providers**: Encrypted API connections through the LiteLLM proxy layer
### Certificate Validation
The platform validates server certificates for all external connections, protecting against man-in-the-middle attacks.
Standard library TLS implementations ensure:
- **Certificate chain verification** against trusted root CAs
- **Hostname validation** matching certificate Subject Alternative Names
- **Revocation checking** where supported by the service
## Internal Communication
Communication between Docker containers within the same deployment uses the internal Docker network. While this traffic
is isolated from external networks by Docker's network segmentation, it is **not encrypted at the application layer**.
The security model relies on:
- **Network isolation**: Container-to-container traffic never traverses external networks
- **Firewall boundaries**: The Docker host's network stack provides isolation from external access
- **Physical/virtual security**: The VM or host environment provides the security perimeter
For deployments requiring encrypted inter-service communication (e.g., multi-host deployments), additional measures such
as service mesh or IPsec can be implemented.
## WebSocket Connections
Real-time event streaming via WebSocket connections is secured using:
- **WSS (WebSocket Secure)**: TLS-encrypted WebSocket protocol for client connections
- **Origin validation**: Verifies the origin header to prevent cross-site WebSocket hijacking
- **Session-based authentication**: Requires valid authentication before upgrading to WebSocket
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance.md
title: "Compliance and regulations"
description: ""
---
# Compliance and regulations
The platform provides technical measures to support regulatory compliance. Organizations using the platform act as data
controllers and remain responsible for their own compliance.
## Coverage
This section documents the platform's compliance capabilities:
- **Data retention policies**: How the platform stores and expires data
- **GDPR compliance**: Support for EU data protection requirements
- **Swiss DSG**: Switzerland-specific data protection features
- **EU AI Act**: AI-specific regulatory considerations
- **Internationalization**: Multi-language support for Swiss organizations
- **Data subject access requests**: Handling data subject rights
## Data controller responsibility
The platform provides technical capabilities like audit logging, configurable retention, encryption, and access
controls. Organizations must configure these features appropriately for their regulatory requirements and use cases.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance/1_data_retention.md
title: "Data Retention Policies"
description: ""
---
## Data retention strategy
The platform implements a tiered retention strategy:
**Ephemeral data (30-day automatic deletion)**: High-performance working memory stored in Redis expires automatically.
Execution-specific data provides a fixed 30-day window for debugging. Conversational memory uses a sliding 30-day
expiration that resets with each access.
**Workflow events (dual constraints)**: NATS JetStream manages workflow events with both time-based (30 days) and
capacity-based (10 million messages) limits. In high-throughput deployments, events may be deleted before the 30-day
limit when capacity is reached.
**Permanent storage (manual lifecycle management)**: NoSQL storage retains conversation history indefinitely without
automatic expiration. Organizations must implement explicit data lifecycle policies aligned with regulatory requirements
and business needs.
**Operational implications**: Organizations have a 30-day window for forensic analysis of workflow execution details.
Critical execution information should be persisted to permanent storage before the 30-day threshold for long-term
retention. Compliance investigations requiring workflow reconstruction are limited to the available retention window.
**Dagster operational logs (separate retention track)**: Verbose Python logger entries (`DEBUG`, `INFO`, `WARNING`)
emitted by pipelines and agents and transient framework-internal events (`HANDLED_OUTPUT`, `LOADED_INPUT`,
`ENGINE_EVENT`, `ASSET_MATERIALIZATION_PLANNED`, `STEP_OUTPUT`) are pruned automatically by the platform's continuous
maintenance subsystem to prevent unbounded Postgres growth. Default retention windows are 7 days for `DEBUG`, 60 days
for `INFO`/`WARNING`, and 30 days for transient framework events; configurable per deployment via
`DAGSTER_*_LOG_RETENTION_DAYS` environment variables. Asset materializations, step success/failure, and run records are
**never** pruned by this subsystem — they remain available for the lifetime of the deployment unless deleted explicitly.
See [Backup and Recovery](../../3_deployment_guide/4_backup_and_recovery/#continuous-postgres-maintenance) for details.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance/2_gdpr.md
title: "GDPR Compliance"
description: ""
---
# GDPR compliance
The platform provides technical measures to support GDPR compliance. Organizations using the platform act as data
controllers and remain responsible for their own compliance.
## Applicability to Swiss organizations
GDPR applies to Swiss organizations when processing personal data of individuals in the EU if the processing relates to:
- Offering goods or services to EU residents (regardless of payment)
- Monitoring behavior of individuals in the EU
Organizations must comply with GDPR requirements even without an EU establishment when these conditions are met.
## Legal basis for processing
Article 6 GDPR requires a legal basis for all personal data processing. At least one of the following must apply:
- Consent: The data subject has given clear consent for specific purposes
- Contract: Processing is necessary for contract performance or pre-contractual measures
- Legal obligation: Processing is necessary to comply with legal requirements
- Vital interests: Processing is necessary to protect life or physical integrity
- Public task: Processing is necessary for tasks in the public interest or official authority
- Legitimate interests: Processing is necessary for legitimate interests, except where overridden by data subject rights
(not available for public authorities)
Organizations must document their legal basis and inform data subjects accordingly.
## GDPR principles
Article 5 GDPR establishes six core principles for processing personal data, plus an accountability requirement:
### Lawfulness, fairness and transparency
The platform provides audit trails, source attribution, and Langfuse tracing for transparency. Organizations must
document their legal basis for processing, provide privacy notices, maintain records of processing activities, and
conduct data protection impact assessments. Processing must be lawful, fair, and transparent to data subjects.
### Purpose limitation
Data must be collected for specified, explicit and legitimate purposes and not further processed in a manner
incompatible with those purposes. Organizations should define clear purposes for each data collection and processing
activity.
### Data minimisation
Multi-tenant isolation, role-based access control, and namespace isolation restrict data access to what is necessary.
Data collected must be adequate, relevant, and limited to what is necessary for the defined purposes.
### Accuracy
Version control tracks data changes to maintain accuracy. Organizations must ensure personal data is accurate and, where
necessary, kept up to date. Inaccurate data must be erased or rectified without delay.
### Storage limitation
Ephemeral data expires automatically after 30 days. Organizations configure retention periods for permanent storage.
Data must be kept in a form that permits identification of data subjects for no longer than necessary for the processing
purposes.
### Integrity and confidentiality
The platform requires TLS/SSL encryption and supports OAuth, OIDC, and SAML authentication. Role-based access control,
container security, and input validation protect data integrity. Processing must ensure appropriate security, including
protection against unauthorized or unlawful processing and accidental loss, destruction, or damage.
### Accountability
Controllers must be able to demonstrate compliance with all principles. The platform supports this through comprehensive
audit logging, documentation capabilities, and traceability features.
## Data subject rights
### Right of access (Art. 15)
Users can request copies of their personal data, processing details, recipients, retention periods, and data sources.
The platform provides a user profile API and audit log access.
### Right to rectification (Art. 16)
Users can request corrections to inaccurate data. Administrators can update user profiles through the API. Thread
messages and audit logs remain immutable to preserve audit trails.
### Right to erasure (Art. 17)
Users can request deletion of data when it's no longer necessary, consent is withdrawn, or processing is unlawful. The
platform supports removing users from threads, and ephemeral data deletes automatically after 30 days.
Exceptions apply when processing is necessary for:
- Freedom of expression and information
- Compliance with legal obligations or tasks in the public interest
- Public health reasons
- Archiving, scientific or historical research, or statistical purposes (when deletion would make these impossible or
seriously impair them)
- Establishment, exercise, or defence of legal claims
### Right to data portability (Art. 20)
Users can request their data in machine-readable format. This applies to data the user provided directly (messages,
uploads), not AI-generated responses, analytics, or derived data. The right applies only when processing is based on
consent or contract and carried out by automated means.
### Right to restriction (Art. 18)
Users can request suspension of processing while verifying data accuracy or assessing objections. Administrators can
suspend accounts through role-based access control.
### Right to object (Art. 21)
Users can object to processing based on legitimate interests. Permission revocation through role-based access control
stops processing.
## Technical measures
The platform implements privacy by design with mandatory TLS/SSL encryption, default-deny access control, automatic
audit logging, 30-day ephemeral data deletion, and minimal data collection. See
[Authentication](../../20_security/1_authentication/), [Encryption](../../20_security/5_data_encryption/), and
[Access Control](../../11_access_management/) for details.
## International data transfers
### EU adequacy decision for Switzerland
Switzerland has an EU adequacy decision (confirmed January 2024), meaning the European Commission recognizes Swiss data
protection law as providing an adequate level of protection. This allows personal data to flow freely from the EU to
Switzerland without additional safeguards.
For organizations hosting in Switzerland, this simplifies compliance with both GDPR and Swiss DSG requirements. See
[Deployment Options](../../3_deployment_guide/1_deployment_options/) for hosting configurations.
### Transfers to other countries
Transfers to countries without an adequacy decision require appropriate safeguards:
- Standard contractual clauses (SCCs) approved by the European Commission
- Binding corporate rules (BCRs)
- Approved codes of conduct or certification mechanisms
- Specific derogations (consent, contract necessity, vital interests, etc.)
## Data breach notification
Article 33 GDPR requires notifying the supervisory authority **without undue delay and, where feasible, not later than
72 hours** after becoming aware of a breach that is likely to result in a risk to individuals' rights and freedoms. If
notification is not made within 72 hours, reasons for the delay must be provided. Notification is not required if the
breach is unlikely to result in a risk.
The notification must include the nature of the breach, affected data subjects, likely consequences, and remedial
measures taken. Data subjects must be informed directly (Article 34) when the breach is likely to result in a high risk
to their rights and freedoms.
The platform provides audit logs, user access reports, monitoring, alerting, and backup capabilities to support breach
investigation, documentation, and response.
## Related documentation
- [Swiss DSG](../3_dsg/)
- [DSAR Procedures](../6_data_subject_requests/)
- [Data Retention](../1_data_retention/)
- [GDPR Full Text](https://gdpr-info.eu/)
- [EDPB Guidelines](https://edpb.europa.eu/)
______________________________________________________________________
:::info Legal disclaimer
This is technical documentation, not legal advice. Consult your data protection officer or legal counsel.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance/3_dsg.md
title: "Swiss Data Protection Act (DSG)"
description: ""
---
# Swiss Data Protection Act (revDSG)
The revised Swiss Federal Act on Data Protection (revDSG/FADP) came into force on September 1, 2023. The revision was
designed to align with EU data protection standards while maintaining Swiss-specific approaches to certain requirements.
:::info
See [GDPR Compliance](../2_gdpr/) for shared requirements. This document covers Swiss-specific differences only.
:::
:::warning When GDPR also applies
Swiss organizations must comply with both revDSG and GDPR when offering goods/services to EU residents or monitoring EU
individuals' behavior. See [GDPR Applicability](../2_gdpr/#applicability-to-swiss-organizations).
:::
## Key differences from GDPR
| Aspect | revDSG | GDPR |
| ------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Fines | Up to CHF 250K on individuals (not companies) | Up to €20M or 4% revenue on companies |
| DPO | Not required | Often mandatory |
| Legal basis | No explicit legal basis required (different approach) | Explicit legal basis mandatory (Art. 6) |
| Breach notification | "As quickly as possible" if high risk (no statutory deadline) | Without undue delay, where feasible within 72 hours if risk exists |
| Sensitive data | Includes administrative/criminal proceedings + social security data | 9 special categories |
| Scope | Only natural persons (legal entities excluded since 2023) | Only natural persons |
## revDSG-specific requirements
### High-risk profiling
The revDSG requires oversight for automated evaluation of personal aspects like risk assessment and behavioral
prediction. The platform provides human-in-the-loop capabilities, Langfuse tracing, and source attribution to support
this requirement. Organizations must identify high-risk profiling activities, conduct data protection impact
assessments, and implement appropriate human oversight.
### Data processing register
Organizations must maintain a register of processing activities. This is an organizational requirement that doesn't need
platform features.
### Data subject rights
Data subject rights work the same as GDPR with minor differences. Response time is 30 days rather than 1 month. The
"right to be forgotten" terminology isn't used, but the erasure right exists. Portability requirements are simpler than
GDPR. See [GDPR documentation](../2_gdpr/#data-subject-rights) for details on how the platform supports these rights.
### Data breach notification
The revDSG requires notifying the Federal Data Protection and Information Commissioner "as quickly as possible" when a
breach is likely to result in high risk to personality rights or fundamental rights (Article 24). Unlike GDPR, Swiss law
does not specify a statutory deadline. However, legal practice generally interprets this as aligning with GDPR's 72-hour
expectation. The threshold for notification (high risk) is stricter than GDPR's general risk threshold. The platform
provides audit logs, monitoring, and alerting to support breach investigation and notification.
### Privacy by design
The revDSG now explicitly requires privacy by design. The platform implements this through mandatory TLS/SSL encryption,
default-deny access control, 30-day automatic deletion of ephemeral data, and audit logging.
## Swiss hosting and adequacy decision
Switzerland has an EU adequacy decision (confirmed January 2024), allowing personal data to flow freely between the EU
and Switzerland without additional safeguards. This means Swiss hosting simplifies compliance for organizations subject
to both GDPR and revDSG requirements.
For pure Swiss operations, hosting data in Switzerland also avoids international transfer requirements under the revDSG.
The platform supports on-premise and Swiss cloud deployment. See
[Deployment Options](../../3_deployment_guide/1_deployment_options/) and
[GDPR International Transfers](../2_gdpr/#international-data-transfers).
## Data transfers
Data transfers require adequate protection in the destination country, appropriate safeguards like standard contractual
clauses, or explicit consent. Swiss hosting avoids these requirements. Organizations can also use Swiss or EU LLM
providers through LiteLLM.
## Related documentation
- [GDPR](../2_gdpr/)
- [DSAR](../6_data_subject_requests/)
- [Data Retention](../1_data_retention/)
- [FDPIC](https://www.edoeb.admin.ch/)
- [revDSG Text](https://www.admin.ch/opc/en/classified-compilation/19920153/)
______________________________________________________________________
:::info Legal disclaimer
This is technical documentation, not legal advice. Consult legal counsel or the Federal Data Protection and Information
Commissioner.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance/4_ai_act.md
title: "EU AI Act"
description: ""
---
# EU AI Act
The EU AI Act establishes requirements for AI systems based on their risk level. The Act was published in the Official
Journal on July 12, 2024, and entered into force on August 1, 2024.
## Implementation timeline
The Act has a phased implementation:
- Prohibited practices: February 2, 2025
- General-purpose AI models: August 2, 2025
- High-risk systems: August 2, 2026
- High-risk systems in regulated products: August 2, 2027
Documentation on platform compliance measures will be added as implementation requirements become clear.
## Risk classification
The AI Act classifies systems by risk:
- **Prohibited**: Manipulative AI, social scoring, real-time biometric identification (with exceptions)
- **High-risk**: Systems in sensitive areas (employment, education, law enforcement, critical infrastructure)
- **Limited risk**: Chatbots and systems requiring transparency
- **Minimal risk**: Most other AI applications
Most platform use cases fall into the limited or minimal risk categories, though specific implementations may vary.
## Platform considerations
The platform provides technical measures that support compliance:
- Audit logging and traceability
- Human-in-the-loop capabilities
- Langfuse tracing for transparency
- Source attribution for AI outputs
- Configurable access controls
Detailed compliance guidance will be added as regulatory requirements clarify.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance/5_internationalization.md
title: "Internationalization"
description: ""
---
# Internationalization
The platform supports Switzerland's four national languages: German, English, French, and Italian. Users can work in
their preferred language throughout the interface.
## Language support
German serves as the default language, but all four languages work identically. The interface, error messages, help
text, and navigation all include translations.
All user-facing text has translations - form validation, dialogs, notifications, error messages, help documentation, and
instructions. Users see no untranslated elements or mixed languages.
Dynamically generated content also respects language preferences. Service descriptions, agent names, and knowledge
namespace labels adapt to the selected language.
Users select their language during authentication or in profile settings. The selection persists across sessions.
## Language selection and switching
Users configure their language in their profile. This becomes the default for all sessions across devices.
Users can temporarily switch languages for a specific session. This helps multilingual users who prefer different
languages for different tasks.
Language changes apply immediately without reloading the page. All text updates instantly.
URLs don't include language codes. Shared links show content in the recipient's preferred language, not the sender's.
## Translation quality
The platform maintains consistent terminology across all languages. Common terms like "agent," "thread," "namespace,"
and "process" use the same translations throughout the interface.
Specialized terminology for technical AI concepts and Swiss regulatory terms receives appropriate translations for each
language.
If a translation is missing in the selected language, the platform falls back to German.
## Localized content handling
User-created resources can include translations for all supported languages. Administrators can provide a knowledge
namespace name and description in German, English, French, and Italian.
The system can automatically detect document language for processing and knowledge management. This enables
language-specific search optimization and retrieval.
Numbers, dates, times, and currencies format according to locale conventions. German users see dates as "31.12.2024,"
while English users see "12/31/2024."
Search functionality uses language-specific tokenization and stemming. German searches use German linguistic rules,
French searches use French rules.
## Custom translations
Organizations can add custom translations for organization-specific terms like internal product names, custom roles, or
specialized process types. Administrators can modify translations through the platform's administrative interface.
## Accessibility and internationalization
Screen reader users in all languages get natural language descriptions instead of English-only alternatives.
Different languages use different text lengths. German translations are often longer than English. The interface
accommodates this expansion without breaking layouts.
The translation architecture can extend to right-to-left languages if needed, though current supported languages all use
left-to-right text.
Accessibility conventions vary by locale. The platform can adapt features to match user expectations in different
language communities.
## Swiss organizations
Swiss public sector institutions often must provide services in multiple national languages. The platform's
four-language support helps meet these requirements without custom development.
Organizations operating across Swiss language regions can deploy a single platform instance. Users in each region see
the interface in their preferred language.
Training materials and documentation can be provided in users' native languages.
All four languages work identically. No linguistic community gets a degraded experience.
The translation architecture allows organizations to add languages if needed - regional dialects, languages of immigrant
communities, or languages of international branches.
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/21_compliance/6_data_subject_requests.md
title: "Data Subject Access Requests (DSAR)"
description: ""
---
# Data subject access requests
Data subjects have rights under GDPR (Articles 15-21) and Swiss revDSG (Articles 25, 32) to access, correct, delete, and
control their personal data. This section explains what the platform supports for these rights.
## Response times
Organizations must respond to data subject requests within one month under GDPR or 30 days under Swiss revDSG. Some
requests can be processed immediately, while others require manual data compilation.
## Access requests
Data subjects can request copies of their personal data, including processing purposes, categories, recipients,
retention periods, and data sources. The platform stores this information in user profiles, conversation threads, and
audit logs. Organizations verify the requester's identity before providing data.
::: warning Compiling chat data for a request
By default no administrator can export another user's conversations or list their uploaded files from the chat interface
\- that data is scoped to its owner. Compiling it for a request therefore needs one of: the data subject exporting their
own conversations, temporary use of the administrative override (`OPENWEBUI_ENABLE_ADMIN_EXPORT` and
`OPENWEBUI_BYPASS_ADMIN_ACCESS_CONTROL`, see [Chat UI feature overview](../../10_chat_ui/1_feature_overview/)), or
direct database access. Plan for this when setting internal response-time targets.
:::
## Rectification
Data subjects can request corrections to inaccurate data. Administrators can update user profiles through the platform's
API. Thread messages and audit logs remain immutable to preserve audit trails.
## Erasure
Data subjects can request deletion when data is no longer necessary, consent is withdrawn, or processing is unlawful.
Exceptions apply for legal obligations, archiving, research, or legal claims. The platform supports removing users from
threads. Ephemeral data deletes automatically after 30 days.
## Restriction
Data subjects can request suspension of processing while verifying data accuracy or assessing objections. Administrators
can suspend accounts through the platform's access control system, preventing resource access while preserving data.
## Portability
Data subjects can request their data in machine-readable format. This applies only to data the subject provided
directly, like messages and uploads, not AI-generated responses, analytics, or derived data. The right applies when
processing is based on consent or contract and carried out by automated means.
## Objection
Data subjects can object to processing based on legitimate interests. Organizations revoke permissions through the
platform's access control to stop processing. Organizations must assess whether overriding legitimate interests exist.
## Related documentation
- [GDPR Compliance](../2_gdpr/)
- [Swiss DSG](../3_dsg/)
- [Data Retention](../1_data_retention/)
______________________________________________________________________
:::info Legal disclaimer
This is technical documentation, not legal advice. Consult your data protection officer or legal counsel.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/2_platform/22_external_integrations.md
title: "External system integrations"
description: ""
---
# External system integrations
The Swiss AI Hub connects with external systems through four integration patterns.
## Integration approaches
### 1. Direct agent API calls
Agents can call external APIs (REST, SOAP, GraphQL, etc.) directly from their workflow steps using standard Python HTTP
libraries like `httpx` or `aiohttp`. During execution, agents make API calls as part of their logic, process responses,
and incorporate the results into their outputs.
This works well for simple, single-operation API calls within agent workflows. An agent might retrieve customer data
from a CRM during conversation, submit form data to an external portal after user approval, or query a ticketing system
to answer questions. The [Agent Developer README](https://github.com/bbvch-ai/aihub-core/tree/main/aihub_agent) has
implementation patterns and examples.
### 2. Platform API integration (external systems calling in)
External systems can trigger Swiss AI Hub agents through the
[Agent Interaction REST API](../18_api/2_agent_interaction_api/). The API authenticates incoming HTTP requests,
translates them into internal events, and streams agent responses back as structured results.
This approach fits bidirectional integrations where external systems need to trigger AI capabilities. A document portal
might trigger AI classification when files are uploaded, a web application might request AI-generated summaries for its
dashboard, or an external workflow system could delegate analysis tasks to AI agents.
### 3. Data pipeline integration (batch synchronization)
[Data Pipelines](../6_pipelines/) continuously synchronize data from external systems into Swiss AI Hub knowledge bases.
Dagster pipelines connect to external data sources, extract and transform the data, then load it into Swiss AI Hub where
it's indexed for RAG (Retrieval-Augmented Generation). Pipelines can run on schedules or be triggered by events.
This handles read-heavy integrations where AI primarily analyzes external data, large-scale document indexing, or
scheduled data synchronization from enterprise systems. You might sync SharePoint documents nightly into a knowledge
base, continuously ingest support tickets for trend analysis, or import product catalogs on a schedule for customer
service agents.
### 4. MCP integration (development tools)
[Model Context Protocol (MCP)](../19_mcp/) lets AI coding assistants like Claude Code, Gemini CLI, and Cursor interact
with Swiss AI Hub during development. This provides read-only observation of platform state for development and
debugging workflows.
## Choosing the right approach
| Approach | Latency | Direction | Complexity | Best for |
| ------------------------- | --------- | ------------- | ----------- | -------------------------------------- |
| Direct agent API calls | Real-time | Outbound | Low | Simple API calls within agent logic |
| Platform API integration | Real-time | Inbound | Medium | External systems triggering AI |
| Data pipeline integration | Batch | Inbound | Medium-High | Large-scale data sync, knowledge bases |
| MCP integration | Real-time | Bidirectional | Low | Development tools only |
## Network and security considerations
### Outbound connectivity (for direct agent API calls and pipelines)
The Swiss AI Hub VM needs outbound HTTPS (port 443) access to external systems. Configure firewall rules to allow
outbound connections to specific endpoints. The platform supports API keys, OAuth tokens, and certificate-based
authentication. All external connections use encrypted HTTPS.
[Network Requirements](../3_deployment_guide/7_network_requirements/) has more details.
### Inbound connectivity (for platform API integration)
External systems connect to Swiss AI Hub using standard HTTPS (port 443). Authentication options include OAuth 2.0, API
keys, or Azure AD integration. Traefik reverse proxy provides built-in rate limiting protection, and Let's Encrypt
handles automatic certificate management for TLS termination.
[Network Security](../20_security/4_network_security/) covers the security architecture.
## General integration principles
Match your integration approach to latency, volume, and direction requirements. Deploy Swiss AI Hub in Switzerland if
you need Swiss data residency. Use TLS encryption, RBAC, and comprehensive audit logging. Leverage enterprise SSO
through OAuth 2.0, SAML, or Azure AD. Configure proper firewall rules for inbound and outbound connectivity. Follow
[Swiss Data Protection](../21_compliance/3_dsg/) guidelines.
## Related documentation
- Agents: [Agent Developer Guide](https://github.com/bbvch-ai/aihub-core/tree/main/aihub_agent) - Implementing direct
API calls
- API: [Agent Interaction REST API](../18_api/2_agent_interaction_api/) - Platform HTTP interface
- Pipelines: [Data Pipelines](../6_pipelines/) - Automated data synchronization
- MCP: [Model Context Protocol](../19_mcp/) - AI assistant integration
- Network: [Network Requirements](../3_deployment_guide/7_network_requirements/) - Firewall and connectivity
- Security: [Network Security](../20_security/4_network_security/) - Security architecture
- Authentication: [Authentication Setup](../11_access_management/1_authentication_setup/) - Configure SSO
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk.md
title: "Part III: The SDK Journey"
description: ""
---
# Part III: The SDK Journey
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/1_quick_start.md
title: "Quick Start"
description: ""
---
# Quick Start Guide for the Swiss AI Hub SDK
This guide will help you get started with the Swiss AI Hub SDK, covering the following topics:
- Setup your development environment
- Create your first agent
- Create your first pipeline
The goal is to have a working agent and pipeline in your development environment that you can build upon in just 30
minutes.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/1_quick_start/1_dev_environment_setup.md
title: "Development Environment Setup"
description: ""
---
# Development environment setup
This page takes you from a clean Windows machine to a running Swiss AI Hub development stack. The infrastructure
(databases, NATS, Keycloak, LiteLLM, Milvus, SeaweedFS, Langfuse and friends) runs in Docker; the code you actually work
on runs from your checkout on the host with hot reload. You edit a Python file and the API restarts, you edit a Vue
component and the browser updates, and nothing gets rebuilt into an image.
The reference environment is Ubuntu on WSL2 with Docker Desktop, because that is what most of the team develops on.
Everything from [Install the toolchain](#step-3-install-the-toolchain) onwards is plain Linux and works identically on a
native Ubuntu box or on macOS.
## Step 1: Install Ubuntu on WSL2
Open PowerShell as Administrator and install a distribution:
```powershell
wsl --install -d Ubuntu-24.04
```
Reboot if prompted, then set your Linux username and password when the Ubuntu console opens. Confirm you are on WSL
version 2:
```powershell
wsl --list --verbose
```
The `VERSION` column must read `2`. WSL 1 cannot run Docker Desktop's engine integration.
::: warning Keep the repository in the Linux filesystem
Clone into your Linux home directory (`~/projects/aihub-core`), never into a Windows path mounted under `/mnt/c`.
Cross-filesystem I/O through `/mnt` is roughly an order of magnitude slower, and inotify events do not propagate, so
`uv sync` crawls and hot reload silently stops working for both the API and the admin UI.
:::
WSL takes half of your host RAM by default, which is not enough for a stack that wants 32 GB. Create
`C:\Users\\.wslconfig` on the Windows side:
```ini
[wsl2]
memory=32GB
processors=8
swap=16GB
```
Apply it with `wsl --shutdown` in PowerShell, then reopen Ubuntu.
## Step 2: Enable the WSL backend in Docker Desktop
Install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/), then open its settings:
1. **General** — tick *Use the WSL 2 based engine*.
2. **Resources → WSL integration** — tick *Enable integration with my default WSL distro*, or enable your Ubuntu
distribution explicitly in the list below it.
3. **Resources → Network** — tick *Enable host networking*.
The third one is not optional. The `open-webui` container runs with `network_mode: host` so that it can reach the API
and the OTEL collector on localhost. Without host networking enabled, that container fails to bind and the chat UI on
port 8080 never comes up, while every other service looks healthy.
Verify from inside the Ubuntu shell, not from PowerShell:
```bash
docker --version
docker compose version
docker run --rm hello-world
```
If `docker` is not found in Ubuntu, the WSL integration toggle did not take; restart Docker Desktop and check the distro
is listed.
## Step 3: Install the toolchain
Everything below runs inside Ubuntu.
```bash
sudo apt update
sudo apt install -y build-essential git curl make
```
Install [uv](https://docs.astral.sh/uv/), which manages both the virtual environment and the Python 3.13 interpreter the
workspace pins:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc
```
Install Node.js 22 or newer and enable pnpm through corepack. The frontends and the documentation site need it; the
Python packages do not.
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
source ~/.bashrc
nvm install 22
corepack enable
```
Check the result:
```bash
uv --version
node --version # v22.x or newer
pnpm --version # 10.x
make --version
docker compose version
```
You do not need to install Python yourself. `uv sync` downloads the interpreter that `requires-python = ">=3.13, <3.14"`
asks for.
## Step 4: Clone and bootstrap
```bash
git clone https://github.com/bbvch-ai/aihub-core.git ~/projects/aihub-core
cd ~/projects/aihub-core
make setup-all
```
`make setup-all` runs two things: `make setup` resolves every workspace package with `uv sync --all-packages` and copies
`.env.dev` to `.env` if you do not already have one, and `make setup-frontend` installs the frontend dependencies with
pnpm. Run them separately if you only want one half.
The generated `.env` is a complete, working development configuration. Every secret in it is a known dev value, and the
Swiss LLM Cloud endpoints are the only entries you may want to fill in for real model access.
::: danger Development credentials only
`.env.dev` contains hard-coded passwords, signing keys and API tokens that are public in the repository, including
`SUPERUSER_PASSWORD='admin'`. It is fine on your machine and unusable anywhere else. Never copy it to a shared or
internet-facing host.
:::
## Step 5: Start the infrastructure
```bash
make up-dev
```
This is `docker compose -f infra/docker-compose.dev.yml --env-file .env up -d --build` and starts around 30 containers.
The first run pulls several gigabytes of images and takes a while; Milvus, Keycloak and Langfuse need another minute or
two after that to report healthy.
Watch it settle:
```bash
docker compose -f infra/docker-compose.dev.yml ps
docker compose -f infra/docker-compose.dev.yml logs -f keycloak
```
Once everything is up, these are published on localhost. Entries with an `http://` prefix open in a browser; the rest
are service endpoints for clients and tooling.
| Service | URL / port | What it is |
| ---------- | ----------------------- | ------------------------------- |
| OpenWebUI | `http://localhost:8080` | Chat interface |
| Keycloak | `http://localhost:8180` | Identity provider (realm aihub) |
| Langfuse | `http://localhost:6006` | LLM tracing and cost tracking |
| Attu | `http://localhost:3003` | Milvus admin UI |
| SeaweedFS | `http://localhost:8889` | Filer UI (S3 gateway on 9000) |
| LiteLLM | `localhost:4000` | LLM gateway |
| NATS | `localhost:4222` | Event backbone |
| Milvus | `localhost:19530` | Vector database |
| FerretDB | `localhost:27017` | Document storage |
| PostgreSQL | `localhost:5432` | Relational storage |
| Valkey | `localhost:6379` | Agent state |
| Neo4j | `http://localhost:7474` | Graph memory |
| MinerU | `localhost:8002` | Document parsing |
Stop it again with `make down-dev` when you are done for the day.
## Step 6: Run only the processes you need
The containers above cover the infrastructure. Which application processes you start on top depends on what you are
working on, and running all of them at once is rarely what you want. Each process needs its own terminal, and every
command below is run from the repository root.
| Working on | Start | Reachable at |
| ------------------------ | ------------------------------------ | --------------------------- |
| Admin UI or the REST API | API + admin UI | `:8000`, `:3333` |
| Tenant administration | sysadmin API + sysadmin UI | `:8001`, `:3334` |
| An agent | API + the agent + a UI to talk to it | `:8000`, `:3333` or `:8080` |
| Document ingestion | pipeline | `:3000` |
| This documentation site | docs dev server, nothing else | `:5173` |
### Admin UI and REST API
```bash
cd packages/api && make run-dev
```
```bash
cd packages/web && pnpm dev
```
Uvicorn with `--reload` on port 8000 (OpenAPI docs at `http://localhost:8000/docs`) and Nuxt on port 3333. The Nuxt dev
server proxies `/api/v1` to `localhost:8000`, so the API has to be running or every request from the UI fails. The port
and the path to the repository-root `.env` are already baked into the `dev` script, so no flags are needed.
### System administration UI and API
```bash
cd packages/sysadmin-api && make run-dev
```
```bash
cd packages/sysadmin-web && pnpm dev
```
The sysadmin plane is a separate deploy artifact and runs on its own ports: the API on 8001 and the UI on 3334.
`packages/sysadmin-web` is a Nuxt layer over `packages/web`, but it proxies `/api/v1` to `localhost:8001`, so it needs
the sysadmin API and not the main one. You do not need the main API or admin UI running to work on tenants, roles and
users.
### Agents
```bash
cd packages/agent && make run-rag-agent
```
The agent subscribes to NATS and registers itself with the platform. `packages/agent/Makefile` has a `run-*` target for
each agent in `packages/agent/app/`, so swap in `run-retrieval-agent` or `run-llm-wrapping-agent` as needed.
The agent itself only needs the Docker infrastructure. To actually send it a message you also need the API, plus either
the admin UI on 3333 or OpenWebUI on 8080.
### Document ingestion pipelines
```bash
make -C packages/pipeline document-ingestion-pipeline
```
Dagster on port 3000. The target loads `.env`, points `DAGSTER_HOME` at `~/.dagster_home` and installs
`dagster.local.yaml` there on first run, so your run history survives restarts. `make playground` and
`make -C packages/pipeline quickstart` run the SDK example pipelines instead, if that is what you are after.
Pair this with a RAG agent when you want the full retrieval loop: ingest documents through Dagster, then query them
through the agent.
### Documentation site
```bash
cd docs && pnpm install
```
```bash
cd docs && pnpm run docs:dev
```
VitePress on port 5173, with the German pages under `/de/`. This is the one thing on this page that needs neither the
Docker stack nor `make setup`: `docs/` is not a pnpm workspace member, it carries its own `pnpm-lock.yaml`, and you
install it from inside the directory rather than with a filter from the root.
::: tip First install is slow on purpose
The workspace sets `minimumReleaseAge`, so pnpm refuses any package published within the last seven days. That is
supply-chain hardening, not a broken lockfile.
:::
## Step 7: Sign in
Open `http://localhost:3333` (or `http://localhost:3334` for the sysadmin UI). You are redirected to Keycloak on port
8180\. Sign in with the superuser from `.env`:
- Username: `admin`
- Password: `admin`
Authentication in development goes through the bundled Keycloak rather than Azure Entra ID, so you need neither an Azure
tenant nor OAuth credentials. The credentials come from `SUPERUSER_USERNAME` and `SUPERUSER_PASSWORD` in `.env.dev`, and
the account carries the `AIHubAccess` and `AIHubSysAdmin` roles, which is what the admin UI, the sysadmin endpoints and
OpenWebUI check for. The same login works everywhere.
## Verify the setup
Check the infrastructure first, then whichever processes you started:
- `docker compose -f infra/docker-compose.dev.yml ps` shows no container in `exited` or `unhealthy`.
- `http://localhost:8000/docs` renders the API reference.
- `http://localhost:3333` signs you in and shows the admin UI.
- `http://localhost:3334` shows the tenant list, if you started the sysadmin pair.
- The agents list in the admin UI contains the agent you started and marks it `online`.
- Sending a message to that agent from `http://localhost:8080` produces a reply.
- `http://localhost:6006` shows a Langfuse trace for that exchange.
## WSL troubleshooting
**A port works in Ubuntu but not in the Windows browser.** WSL2 forwards localhost automatically, but the relay
occasionally stops after the host sleeps. `wsl --shutdown` from PowerShell fixes it. If a specific dev server stays
unreachable, bind it to all interfaces instead: `pnpm dev --host 0.0.0.0` for either frontend, or edit the `--host` flag
in the relevant make target.
**OpenWebUI on 8080 never comes up while everything else is healthy.** Host networking is off in Docker Desktop. See
[Step 2](#step-2-enable-the-wsl-backend-in-docker-desktop).
**`uv sync` is slow and hot reload never fires.** The repository is on `/mnt/c`. Move the clone into the Linux
filesystem; there is no configuration that makes the mounted path fast.
**Containers get OOM-killed or the machine freezes.** WSL is capped below what the stack needs. Raise `memory` in
`.wslconfig` and `wsl --shutdown`.
**Certificate or token errors after the laptop wakes from sleep.** WSL's clock drifts out of sync with the host and
Keycloak rejects the skewed timestamps. Run `sudo hwclock -s` inside Ubuntu, or `wsl --shutdown` and start over.
**Port conflicts on startup.** Something on the Windows side already owns 8080, 5432, 6379 or 4222. Find it with
`netstat -ano | findstr :8080` in PowerShell and stop it, or change the host-side port mapping in
`infra/docker-compose.dev.yml`.
## Next steps
With the stack running, [Your First Agent](../3_your_first_agent/) walks through building an agent against the SDK, and
[Your First Pipeline](../4_your_first_pipeline/) does the same for data ingestion.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/1_quick_start/2_sdk_architecture.md
title: "SDK Architecture"
description: ""
---
# SDK Architecture
[@mhoegger](https://github.com/mhoegger)
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/1_quick_start/3_your_first_agent.md
title: "Your First Agent"
description: ""
---
# Your First Agent
Build your first agent using the Swiss AI Hub Agent (`swiss_ai_hub.agent`) SDK - a simple message processing agent with
a 2-step workflow.
## What you'll learn
This quickstart covers the essential building blocks:
- **Agent structure**: How agents process messages in steps
- **Event flow**: Data flowing between workflow steps
- **Configuration**: Settings that control agent behavior
- **Testing**: Running your agent locally
## Prerequisites
You need the Swiss AI Hub development environment running. Before you start, make sure you completed the
[Development Environment Setup](../1_dev_environment_setup/) steps.
## How agents work
Swiss AI Hub agents are **event-driven workflows** with three essential parts:
- **Steps**: Functions decorated with `@step()` that process events
- **Events**: Data objects flowing between steps
- **Configuration**: Typed settings that control agent behavior
## Some Basic Concepts to start with!
Let's look at the default agent created in the setup of the development environment:
```python
import logging
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.events.agent.control import StopEvent
from swiss_ai_hub.agent.workflow.decorators.step import step
logger = logging.getLogger(__name__)
class MyCustomAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
) -> StopEvent:
content = event.messages[-1].content
print(f"[Step 1]: UserMessageEvent: {content}")
hello_world_message = "Hello World!\n"
return StopEvent(final_message=hello_world_message)
```
When you start the UI and try to use the Agent in the OpenWebUI. You notice that the agent does not respond.

### Use Chunk Events to display live Chat Responses
The reason you don't see a response in the chat interface is that only special events (`DisplayEvents`) are displayed in
the UI. And for Chat interfaces especially the Response is composed from `ChunkEvent`'s. So let's enable our step to
display such a `ChunkEvent`. For this we need to use use the `EventDisplayer` in the step-function and await the
`display_chunk` methodm with a first argument of the content to display, and as a second argument we can pass the source
of this chunk. Usually this is the model name or the Language model that produces this chunk. Since in our case we hard
code the chunk for now let's just use the ClassName of the Agent as source.
```python
import logging
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.events.agent.control import StopEvent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer # [!code ++]
logger = logging.getLogger(__name__)
class MyCustomAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
displayer: EventDisplayer, # [!code ++]
) -> StopEvent:
content = event.messages[-1].content
print(f"[Step 1]: UserMessageEvent: {content}")
hello_world_message = "Hello World!\n"
await displayer.display_chunk(hello_world_message, "MyCustomAgent") # [!code ++]
return StopEvent(final_message=hello_world_message)
```
How we see that the Agent responds with an actual message.

### See the power of streaming
As you might know from other AI Tools, large language models produce their responses chunk by chunk. Instead of just
displaying the response as a whole at the end we can build up the final response part by part which enables us to show
the user some part of the response as quickly as possible. let's demonstrate:
```python
import logging
import asyncio # [!code ++]
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.events.agent.control import StopEvent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
logger = logging.getLogger(__name__)
class MyCustomAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
displayer: EventDisplayer,
) -> StopEvent:
content = event.messages[-1].content
print(f"[Step 1]: UserMessageEvent: {content}")
hello_world_message = "Hello World!\n"
await displayer.display_chunk(hello_world_message, "MyCustomAgent")
await asyncio.sleep(2) # [!code ++]
repeat_message = f"You said: {content}!\n" # [!code ++]
await displayer.display_chunk(repeat_message, "MyCustomAgent") # [!code ++]
return StopEvent(final_message=hello_world_message)
```
We just added a second chunk that is displayed. When you run the agent again now you see that it will responde with
`Hello World!` first and after 2 seconds answers with `You said: Hello!`\
### Add some thinking steps
Especially when the agent need longer to finalize it's result it is good practice to inform the user about what is going
on in the agent. To allow this you can display `ThoughtEvent`'s. Again we use the `EventDisplayer` but this time with
the `display_thought` method.
```python
import logging
import asyncio
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.events.agent.control import StopEvent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
logger = logging.getLogger(__name__)
class MyCustomAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
displayer: EventDisplayer,
) -> StopEvent:
content = event.messages[-1].content
print(f"[Step 1]: UserMessageEvent: {content}")
await displayer.display_thought("Drinking coffee...") # [!code ++]
hello_world_message = "Hello World!\n"
await displayer.display_chunk(hello_world_message, "MyCustomAgent")
await asyncio.sleep(2)
repeat_message = f"You said: {content}!\n"
await displayer.display_chunk(repeat_message, "MyCustomAgent")
return StopEvent(final_message=hello_world_message)
```
Now you see there is an additional section in the response called `Thinking...` if you expand it you can see our thought
that has been created with the content `Drinking coffee...`

## Create your first Multistep Agent
### 1. Create a Custom Event (`events/MyCustomAgentEvent.py`):
First, create an event to pass data between steps:
```python
from typing import Annotated
from swiss_ai_hub.core.events.agent.control import ControlEvent
from pydantic import Field
class MyCustomAgentEvent(ControlEvent):
word_count: Annotated[int, Field(description="The word count of the processed content")]
```
### 2. Adjust Agent Implementation (`MyCustomAgent.py`):
```python
import logging
import asyncio
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.events.agent.control import StopEvent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
from .events.MyCustomAgentEvent import MyCustomAgentEvent # [!code ++]
logger = logging.getLogger(__name__)
class MyCustomAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
displayer: EventDisplayer,
) -> MyCustomAgentEvent:
content = event.messages[-1].content
print(f"[Step 1]: UserMessageEvent: {content}")
await displayer.display_thought("Drinking coffee...")
hello_world_message = "Hello World!\n"
await displayer.display_chunk(hello_world_message, "MyCustomAgent")
await asyncio.sleep(2)
repeat_message = f"You said: {content}!\n"
await displayer.display_chunk(repeat_message, "MyCustomAgent")
word_count = len(content.split()) # [!code ++]
return MyCustomAgentEvent(word_count=word_count) # [!code ++]
return StopEvent(final_message=hello_world_message) # [!code --]
@step() # [!code ++]
async def stop_step( # [!code ++]
self, # [!code ++]
event: MyCustomAgentEvent, # [!code ++]
displayer: EventDisplayer, # [!code ++]
) -> StopEvent: # [!code ++]
await displayer.display_chunk(f"The word count is {event.word_count} words\n", "MyCustomAgent") # [!code ++]
return StopEvent() # [!code ++]
```
Now you have an first agent that acts in two steps. in the first step we do everything we have done in the past but also
we count the number of words in the user message. This information is then passed to a second step where we also add to
the response `The word count is X words` where X is the number of words we have counted in the first step. We have
connected the two steps by defining out new Event `MyCustomAgentEvent` as the output of the first step and as input to
the second step.
If you Navigate to the Agent Overview, select you agent there and then go to `Workflow`, then you can see the Workflow
and the steps of your agent. You can see which steps are defined and which input and output events these steps have.

### 3. Add some Agent Configuration (`MyCustomAgentConfig.py`):
Often you want to have your agent configurable when you start it. For this you can use the Configuation Class. When you
set up your agent using the CLI then a basic Configuration File has already been created for you looking like this:
```python
from typing import Annotated
from pydantic import Field
from swiss_ai_hub.core.agents.agent_config import AgentConfig
class MyCustomAgentConfig(AgentConfig):
"""Configuration class for My First Agent Agent."""
config_value: Annotated[str, Field(
default="Default Config Value",
description="Some configuration value for the agent"
)]
```
We can access this configuration in every step if we need to. For example can we read the content of the field
`config_value` in the second step of our agent and post it's string value also as a chunk. However usually you use the
config to configure some logic in your steps, either with system-prompts or configurations to some methods.
```python
import logging
import asyncio
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.events.agent.control import StopEvent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
from .events.MyCustomAgentEvent import MyCustomAgentEvent
from .MyCustomAgentConfig import MyCustomAgentConfig # [!code ++]
logger = logging.getLogger(__name__)
class MyCustomAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
displayer: EventDisplayer,
) -> MyCustomAgentEvent:
content = event.messages[-1].content
print(f"[Step 1]: UserMessageEvent: {content}")
await displayer.display_thought("Drinking coffee...")
hello_world_message = "Hello World!\n"
await displayer.display_chunk(hello_world_message, "MyCustomAgent")
await asyncio.sleep(2)
repeat_message = f"You said: {content}!\n"
await displayer.display_chunk(repeat_message, "MyCustomAgent")
word_count = len(content.split())
return MyCustomAgentEvent(word_count=word_count)
@step()
async def stop_step(
self,
event: MyCustomAgentEvent,
config: MyCustomAgentConfig, # [!code ++]
displayer: EventDisplayer,
) -> StopEvent:
await displayer.display_chunk(f"The word count is {event.word_count} words\n", "MyCustomAgent")
await displayer.display_chunk(f"My config sais: {config.config_value}", "MyCustomAgent")
return StopEvent()
```
You can set the configuration values in your `trigger.py` or when you build the agent the in the `main.py`
```python{10}
async def main():
runner = AgentRunner(
agent_type=MyCustomAgent,
agent_config=MyCustomAgentConfig(
agent_class=MyCustomAgent.__name__,
agent_id="my_custom_agent",
name=LocaleString(en="My Custom Agent"),
description=LocaleString(en="This is a simple agent created from a template."),
config_value="My first Config Value"
),
)
await runner.run_forever()
if __name__ == "__main__":
asyncio.run(main())
```
### 4. Test Script (`trigger.py`):
## Run and debug your agent
1. **Run the test script**:
To quickly test your agent you can write a `trigger.py` script which starts the agent and posts it's StartEvent. This
way you can test the agent without any UI.
::: code-group
```python [trigger.py]
import asyncio
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.events.agent.user import UserMessageEvent
from swiss_ai_hub.core.testing.auth_utils import fake_user
from swiss_ai_hub.agent.runners.agent_test_runner import AgentTestRunner
from llama_index.core.base.llms.types import ChatMessage, MessageRole
from MyAgent import MyAgent
from MyAgentConfig import MyAgentConfig
# Enable detailed logging to see event flow
enable_logging()
async def main():
# Configure the agent
config = MyAgentConfig(
agent_class=MyCustomAgent.__name__,
agent_id="my_custom_agent",
name=LocaleString(en="My Custom Agent"),
description=LocaleString(en="This is a simple agent created from a template."),
config_value="My first Config Value"
)
# Create test runner
runner = AgentTestRunner(agent_type=MyAgent, agent_config=config)
# Run the agent with a test message
async with runner.test_run() as topic:
await runner.send_event_from_topic(
topic=topic,
start_event=UserMessageEvent(
messages=[ChatMessage(
content="Hello world this is my first agent",
role=MessageRole.USER
)],
user=fake_user()
)
)
print(f"Agent completed: {runner.has_stop_event}")
if __name__ == "__main__":
asyncio.run(main())
```
```bash
python trigger.py
```
Expected output:
```
[Step 1] Processing message: 'Hello world this is my first agent' -> 'HELLO WORLD THIS IS MY FIRST AGENT'
[Step 2] Creating response: 'Processed: HELLO WORLD THIS IS MY FIRST AGENT (Words: 7)'
Agent completed: True
```
2. **Debug with Langfuse Tracing** - Open `http://localhost:6006` to see:
- Step-by-step execution flow
- Event data flowing between steps
- Timing and performance metrics
- Event payload details
3. **Check the logs** - The `enable_logging()` call shows real-time event flow and helps debug issues.
## Understanding the workflow
Your agent follows this event flow:
1. **UserMessageEvent** → `process_message()` → **MessageEvent**
2. **MessageEvent** → `create_response()` → **StopEvent**
Each step:
- Receives an event as input
- Processes the data
- Returns a new event
- The workflow engine routes events to the next step
## What you learned
- **Event-driven workflows**: Steps process events and produce new events
- **Custom events**: Creating typed data objects to pass between steps
- **Configuration**: Using typed settings to control agent behavior
- **Testing**: Use `AgentTestRunner` for isolated testing
- **Debugging**: Langfuse tracing and logging for observability
## Next steps
- [Your First Pipeline](../4_your_first_pipeline/) -
- [Building Agents](../../2_building_agents/) - Learn more advanced agent patterns
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/1_quick_start/4_your_first_pipeline.md
title: "Your First Pipeline"
description: ""
---
# Your First Pipeline
Build your first data processing pipeline using the Swiss AI Hub Pipeline (`swiss_ai_hub.pipeline`) SDK - a complete
data transformation pipeline with multiple connected assets.
## What you'll learn
This quickstart covers the essential building blocks:
- **Asset structure**: How pipelines process data through connected assets
- **Data flow**: How data flows between assets automatically
- **Configuration**: Settings and resources that control pipeline behavior
- **Testing**: Running your pipeline locally and in Dagster UI
- **Observability**: Monitoring pipeline execution with built-in tools
## Prerequisites
You need the Swiss AI Hub development environment running. Before you start, make sure you completed the
[Development Environment Setup](../1_dev_environment_setup/) steps.
## How pipelines work
Swiss AI Hub pipelines are **data processing workflows** built on Dagster with three essential parts:
- **Assets**: Functions that create, transform, or consume data
- **Dependencies**: Automatic data flow between assets based on function parameters
- **Resources**: Shared configuration and services to external systems
## Create your first pipeline
Let's build a data pipeline that processes user feedback data.
### Start with a simple pipeline
First, let's understand pipeline basics with a minimal example:
#### 1. Create your basic assets (`simple_pipeline.py`):
```python
from dagster import AssetExecutionContext, Output, asset
@asset(description="Raw text data source")
def raw_feedback_data(context: AssetExecutionContext) -> Output[str]:
"""Source asset that provides raw user feedback data."""
feedback = "The product is amazing but the documentation could be better!"
context.log.info(f"Loaded raw feedback: {feedback}")
return Output(feedback, metadata={"feedback": feedback})
@asset(description="Cleaned and processed feedback")
def cleaned_feedback(context: AssetExecutionContext, raw_feedback_data: str) -> Output[dict]:
"""Transform raw feedback into structured data."""
# Simple processing: clean text and extract basic metrics
text = raw_feedback_data.strip().lower()
words = text.split()
processed = {
"original_text": raw_feedback_data,
"cleaned_text": text,
"word_count": len(words),
"sentiment": "positive" if "amazing" in text else "neutral",
}
context.log.info(f"Processed feedback: {processed}")
return Output(
processed,
metadata={
"original_text": processed["original_text"],
"cleaned_text": processed["cleaned_text"],
"word_count": processed["word_count"],
"sentiment": processed["sentiment"],
},
)
```
#### 2. Add the pipeline definition (`simple_pipeline.py`):
```python
from dagster import Definitions
## ... your asset definitions from above ...
# Basic pipeline definition
defs = Definitions(assets=[raw_feedback_data, cleaned_feedback])
```
#### 3. Run your basic pipeline:
```bash
uv run dagster dev -f simple_pipeline.py
```
Open `http://localhost:3000` and you'll see:
- **Asset lineage graph**: raw_feedback_data → cleaned_feedback
- **Materialize buttons** to execute assets
- **Asset details** showing inputs, outputs, and execution logs
Click **"Materialize all"** to run the pipeline and see the data flow!
## Build a real Swiss AI Hub pipeline
Now let's create a realistic pipeline using the `swiss_ai_hub.pipeline` SDK that demonstrates document processing
patterns. We'll break this down step by step to understand each component.
### 1. Understanding Swiss AI Hub Pipeline Structure
Swiss AI Hub pipelines follow these key patterns:
- **Asset Factories**: Reusable functions that create configured assets
- **Resources**: Configured services like parsers, stores, and embedding models
- **Dynamic Partitions**: Each document becomes a separate partition for parallel processing
### 2. Set up your pipeline configuration
Start by creating the basic configuration and imports (`my_document_pipeline.py`):
```python
from swiss_ai_hub.core.generative_ai.resources.models.llm.embedding_model_config import EmbeddingModelConfig
from swiss_ai_hub.core.generative_ai.resources.models.llm.llm_config import LLMConfig
from dagster import AssetKey, AssetSelection, Definitions, DynamicPartitionsDefinition
# Import Swiss AI Hub pipeline factories
from swiss_ai_hub.pipeline.assets.factories.data_lake_to_vector_store.documents_factory import documents_factory
from swiss_ai_hub.pipeline.assets.factories.data_lake_to_vector_store.nodes_factory import nodes_factory
from swiss_ai_hub.pipeline.assets.factories.data_lake_to_vector_store.observable_data_lake_factory import (
observable_data_lake_factory,
)
# Import Swiss AI Hub resources and utilities
from swiss_ai_hub.pipeline.resources.factory import (
default_io_manager_s3_datalake_resources,
local_mongo_milvus_storage_context_resource,
s3_data_lake_resources,
)
from swiss_ai_hub.pipeline.resources.llm.embedding_model_resource import EmbeddingModelResource
from swiss_ai_hub.pipeline.resources.parser.document_parser_resource import DocumentParserResource, LoaderType
from swiss_ai_hub.pipeline.resources.parser.markdown_structural_node_parser_resource import MarkdownStructuralNodeParserResource
# Pipeline configuration - defines where data flows between assets
DATA_LAKE_KEY = AssetKey(["playground", "data_lake"]) # Raw file storage
DOCUMENT_KEY = AssetKey(["playground", "documents"]) # Parsed documents
NODES_KEY = AssetKey(["playground", "nodes"]) # Document chunks with embeddings
# Storage configuration
CONTAINER_NAME = "playground" # S3 bucket/container name
DIRECTORY_NAME = "documents" # Folder for documents
NAMESPACE_NAME = DIRECTORY_NAME # Vector store namespace
STORE_NAME = CONTAINER_NAME # Document store name
# Dynamic partitions allow parallel processing of individual documents
document_partitions = DynamicPartitionsDefinition(name="document_partitions")
```
### 3. Create your pipeline assets
Next, create the three main assets that form your processing pipeline:
```python
# Create the pipeline assets using Swiss AI Hub factories
# 1. Observable Data Lake - watches for new/changed files
observable_asset = observable_data_lake_factory(
asset_key=DATA_LAKE_KEY,
partitions=document_partitions
)
# 2. Documents Asset - processes raw files into structured documents
documents_asset = documents_factory(
asset_key=DOCUMENT_KEY,
data_lake_key=DATA_LAKE_KEY, # Depends on data lake
partitions=document_partitions # One partition per document
)
# 3. Nodes Asset - chunks documents and creates embeddings
nodes_asset = nodes_factory(
asset_key=NODES_KEY,
document_key=DOCUMENT_KEY, # Depends on documents
partitions=document_partitions # One partition per document
)
# Combine all assets
assets = [observable_asset, documents_asset, nodes_asset]
```
**Understanding the asset factories:**
- `observable_data_lake_factory`: Creates an asset that monitors file changes
- `documents_factory`: Creates an asset that parses files into RefDoc objects with metadata
- `nodes_factory`: Creates an asset that chunks documents and generates vector embeddings
### 4. Configure your pipeline resources
Now configure the resources (services) your pipeline needs:
```python
# Resource configuration - split into logical groups for clarity
# A. Storage and I/O Resources
storage_resources = {
# Data lake I/O managers for S3-compatible storage
**default_io_manager_s3_datalake_resources(
container_name=CONTAINER_NAME,
directory_name=DIRECTORY_NAME
),
# Data lake resources for file management
**s3_data_lake_resources(
container_name=CONTAINER_NAME,
directory_name=DIRECTORY_NAME,
figures_directory_name="__figures__", # For extracted images/figures
),
}
# Models used for chunking: the LLM detects table headers, and the embedding model's
# input limit caps how large a chunk may get.
llm_config = LLMConfig(model_name="azure/gpt-4o-mini")
embedding_config = EmbeddingModelConfig(model_name="azure/text-embedding-3-large")
# B. Document Processing Resources
processing_resources = {
# Document parser - uses AI-powered MinerU for PDF/Word/etc
"document_parser": DocumentParserResource(loader_type=LoaderType.MINERU),
# Node parser - chunks documents using structural elements
"node_parser": MarkdownStructuralNodeParserResource(
llm_config=llm_config,
embedding_config=embedding_config,
),
}
# C. Database and Search Resources
database_resources = {
# Vector store and document store (MongoDB + Milvus)
**local_mongo_milvus_storage_context_resource(
vector_store_uri="http://localhost:19530", # Milvus connection
store_name=STORE_NAME,
namespace_name=NAMESPACE_NAME,
),
}
# D. AI Model Resources
ai_resources = {
# Embedding model for creating vector representations
"embedding_model": EmbeddingModelResource(embedding_config=embedding_config),
}
# Combine all resources
all_resources = {
**storage_resources,
**processing_resources,
**database_resources,
**ai_resources,
}
```
### 5. Define your complete pipeline
Finally, bring everything together in the pipeline definition:
```python
# Define the complete pipeline
defs = Definitions(
assets=assets, # The three processing assets
resources=all_resources, # All configured services
)
```
**Complete pipeline file** (`my_document_pipeline.py`):
Here's the complete file with all components together:
```python
from swiss_ai_hub.core.generative_ai.resources.models.llm.embedding_model_config import EmbeddingModelConfig
from swiss_ai_hub.core.generative_ai.resources.models.llm.llm_config import LLMConfig
from dagster import AssetKey, Definitions, DynamicPartitionsDefinition
from swiss_ai_hub.pipeline.assets.factories.data_lake_to_vector_store.documents_factory import documents_factory
from swiss_ai_hub.pipeline.assets.factories.data_lake_to_vector_store.nodes_factory import nodes_factory
from swiss_ai_hub.pipeline.assets.factories.data_lake_to_vector_store.observable_data_lake_factory import (
observable_data_lake_factory,
)
from swiss_ai_hub.pipeline.resources.factory import (
default_io_manager_s3_datalake_resources,
local_mongo_milvus_storage_context_resource,
s3_data_lake_resources,
)
from swiss_ai_hub.pipeline.resources.llm.embedding_model_resource import EmbeddingModelResource
from swiss_ai_hub.pipeline.resources.parser.document_parser_resource import DocumentParserResource, LoaderType
from swiss_ai_hub.pipeline.resources.parser.markdown_structural_node_parser_resource import MarkdownStructuralNodeParserResource
# Configuration
DATA_LAKE_KEY = AssetKey(["playground", "data_lake"])
DOCUMENT_KEY = AssetKey(["playground", "documents"])
NODES_KEY = AssetKey(["playground", "nodes"])
CONTAINER_NAME = "playground"
DIRECTORY_NAME = "documents"
NAMESPACE_NAME = DIRECTORY_NAME
STORE_NAME = CONTAINER_NAME
llm_config = LLMConfig(model_name="azure/gpt-4o-mini")
embedding_config = EmbeddingModelConfig(model_name="azure/text-embedding-3-large")
document_partitions = DynamicPartitionsDefinition(name="document_partitions")
# Assets
observable_asset = observable_data_lake_factory(DATA_LAKE_KEY, document_partitions)
documents_asset = documents_factory(DOCUMENT_KEY, data_lake_key=DATA_LAKE_KEY, partitions=document_partitions)
nodes_asset = nodes_factory(NODES_KEY, document_key=DOCUMENT_KEY, partitions=document_partitions)
assets = [observable_asset, documents_asset, nodes_asset]
# Resources
defs = Definitions(
assets=assets,
resources={
**default_io_manager_s3_datalake_resources(CONTAINER_NAME, DIRECTORY_NAME),
**s3_data_lake_resources(CONTAINER_NAME, DIRECTORY_NAME, "__figures__"),
**local_mongo_milvus_storage_context_resource("http://localhost:19530", STORE_NAME, NAMESPACE_NAME),
"document_parser": DocumentParserResource(loader_type=LoaderType.MINERU),
"node_parser": MarkdownStructuralNodeParserResource(
llm_config=llm_config,
embedding_config=embedding_config,
),
"embedding_model": EmbeddingModelResource(embedding_config=embedding_config),
},
)
```
### 6. Run your Swiss AI Hub pipeline:
```bash
uv run dagster dev -f my_document_pipeline.py
```
You'll see the complete document processing pipeline:
```
data_lake (observable) → documents → nodes
↓ ↓
(DocStore) (VectorStore)
```
### 7. Understanding the data flow:
1. **Observable Data Lake**: Monitors for new PDF, Word, Markdown, etc. files
2. **Documents**: Parses files using AI-powered document intelligence (MinerU)
3. **Nodes**: Chunks documents using structural parsing and generates embeddings
### 8. Add jobs and scheduling to your pipeline
For production pipelines, you'll want to add jobs and scheduling. Let's extend the pipeline:
```python
# Add these imports to my_document_pipeline.py
from swiss_ai_hub.pipeline.jobs.factory import observe_source_job
from swiss_ai_hub.pipeline.schedules.factory import daily_schedule_at
from swiss_ai_hub.pipeline.sensors.factory import default_automation_sensor
# Create jobs for different operations
observe_job = observe_source_job(
observable_asset=observable_asset,
namespace_name=NAMESPACE_NAME,
)
# Update your pipeline definition to include jobs and schedules
defs = Definitions(
assets=assets,
resources={
# ... your existing resources ...
},
# Add jobs for pipeline operations
jobs=[observe_job],
# Add scheduling - observe daily at midnight
schedules=[daily_schedule_at(observe_job, hour=0, minute=0)],
# Add sensors for automation
sensors=[default_automation_sensor(assets)],
)
```
**Understanding jobs and scheduling:**
- **observe_job**: Manually trigger observation of the data lake
- **daily_schedule_at**: Schedule automatic data lake observation
- **default_automation_sensor**: Automatically trigger asset processing when dependencies change
Your pipeline now supports:
- **Manual execution**: Materialize individual assets in Dagster UI
- **Scheduled observation**: Daily checks for new documents
- **Automatic processing**: Assets process automatically when upstream changes detected
### 9. Monitor with Swiss AI Hub observability tools:
- **Dagster UI** (`http://localhost:3000`): Asset lineage, execution logs, and materialization history
- **MongoDB Compass**: Document store inspection
- **Milvus (Attu)**: Vector database monitoring
::: tip SeaweedFS Filer
In production, the SeaweedFS Filer web UI is accessible at `datalake.${DOMAIN}` (OAuth2 protected, requires the
`AIHubSysAdmin` realm role). In development mode, it's available at `http://localhost:8889` for browsing uploaded files
and debugging storage.
:::
### 10. Understanding Swiss AI Hub pipeline patterns
Your Swiss AI Hub pipeline demonstrates key patterns:
1. **Observable Assets**: Automatically detect new documents without manual intervention
2. **Dynamic Partitions**: Each document is processed independently
3. **Resource Management**: Configurable parsers, models, and storage backends
4. **Automation Policies**: Eager processing when upstream assets change
## What you learned
- **Swiss AI Hub SDK Usage**: Using factories, resources, and typed data objects from `swiss_ai_hub.pipeline`
- **Document Processing Pipeline**: Complete flow from raw files to searchable embeddings
- **Asset Factory Usage**: Using existing factories like `documents_factory` and `nodes_factory`
- **Resource Configuration**: Setting up parsers, LLMs, and storage systems
- **Observability**: Monitoring AI-powered pipelines with Dagster
- **Production Readiness**: Scalable, automated, and maintainable pipeline architecture
## Next steps
- [Building Pipelines](../../3_building_pipelines/) - Learn advanced Swiss AI Hub pipeline patterns
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/1_quick_start/_1_why_our_sdk.md
title: "Why our SDK"
description: ""
---
[@mhoegger](https://github.com/mhoegger)
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents.md
title: "Building Agents"
description: ""
---
# Building Agents with the Swiss AI Hub SDK
An agent in the Swiss AI Hub is a workflow defined by a series of steps that process events. Agents can interact with
users, call external services, and coordinate with other agents to perform complex tasks.
This documentation guides you through the architecture, patterns, and best practices for building robust and scalable
agents.
::: warning
Before you begin, please complete the [Development Environment Setup](../1_quick_start/1_dev_environment_setup/) and
build [Your First Agent](../1_quick_start/3_your_first_agent/).
:::
## What's Covered
This guide is structured to build your knowledge progressively:
01. [**Agent Fundamentals**](./1_agent_fundamentals/) - The core architecture, including events, steps, and
configuration.
02. [**Core Patterns**](./2_core_patterns/) - Essential workflow patterns like conditional logic, loops, and state
management.
03. [**Human in the Loop**](./3_human_in_the_loop/) - Building interactive workflows that require human approval or
input.
04. [**Multi-Agent Systems**](./4_multi_agent_systems/) - Coordinating multiple agents to solve complex problems.
05. [**Memory**](./5_memory/) - Adding persistent memory to your agents for user preferences and organizational
knowledge.
06. [**Testing and Debugging**](./6_testing_and_debugging/) - Best practices for ensuring your agent is reliable and
correct.
07. [**Production Deployment**](./7_production_deployment/) - Guidelines for packaging and deploying your agent.
08. [**Agent Observation**](./8_agent_observation/) - Monitoring your agent's behavior and performance with integrated
tracing.
09. [**Configurable Agent Forms**](./8_configurable_agents/) - Making agent configuration editable through the Admin UI
using the Form Duality Pattern.
10. [**Execution Model**](./9_execution_model/) - How the dispatcher executes steps, synchronization primitives,
anti-patterns, and troubleshooting.
11. [**Events Reference**](./10_events_reference/) - Complete event hierarchy, choosing the right base event, and
available events catalog.
12. [**Using MCP Tools**](./11_using_mcp_tools/) - Connecting agents to external MCP servers to call their tools.
13. [**Capabilities and Tools**](./12_capabilities_and_tools/) - Reusing the steps every chat agent needs and letting
the model choose tools with the tool loop.
## Key Principles of the SDK
The SDK is designed around a few core principles to make development intuitive and scalable:
- **Event-Driven by Nature**: Agents react to a stream of events. This asynchronous, message-based architecture makes
workflows dynamic and resilient.
- **Declarative Workflows**: You define *what* each step does using the `@step` decorator. The SDK automatically handles
the *how* of routing events and wiring your steps together.
- **Managed State**: Handle conversation memory and run-time data effortlessly with injectable `RunContext` and
`ThreadContext` objects, backed by a distributed store.
- **Built for Production**: With strongly-typed configuration, a dedicated testing framework, and built-in
observability.
## The Development Workflow
Building a high-quality agent typically follows these four stages:
::: tip
A core design principle is that each agent should do one thing well. Complex problems are best solved by coordinating
multiple specialized agents.
:::
1. **Design Your Workflow**: Outline your agent's purpose, the events it will handle, and the sequence of steps it will
take to achieve its goal.
2. **Implement the Core Logic**: Write your `Agent` class, define its strongly-typed `AgentConfig`, and implement the
`@step` methods that transform events.
3. **Test and Debug**: Use the `AgentTestRunner` for unit testing and a tracing tool like Langfuse to visually debug the
flow of events through your agent.
4. **Deploy and Monitor**: Package your agent and deploy it to the Swiss AI Hub, where its performance and behavior can
be monitored in real-time.
## Next Steps
Start with [agent fundamentals](./1_agent_fundamentals/) to understand the core architecture, then explore the specific
patterns and techniques in the following sections.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/1_agent_fundamentals.md
title: "Agent Fundamentals"
description: ""
---
# Agent Fundamentals
An agent is a self-contained, event-driven workflow. It processes an input through a series of operations to produce a
final output. The best agents are focused, doing one thing well.
This page covers the essential building blocks and the core mechanics that power every agent.
## How It Works: The Agent Dispatcher
Behind the scenes, a component called the **Agent Dispatcher** orchestrates your workflow. Understanding its three main
jobs makes building agents much easier:
1. **Introspection**: When an agent starts, the dispatcher inspects all methods marked with the `@step` decorator. It
analyzes their parameters and return types to build a map of your workflow before it runs.
2. **Event Routing**: The dispatcher acts as a central router. When one step **returns** an event, the dispatcher
catches it and delivers it to the *next* step that is designed to **accept** that event type. This is how your steps
are automatically chained together.
3. **Dependency Injection**: The dispatcher automatically provides - or "injects" - necessary objects like configuration
and context directly into your step methods based on their type hints. You don't create these objects; you just ask
for them.
With the dispatcher handling the **how** you can focus on the **what**: defining your agent's logic.
## Events: The Data and Control Flow
Events are the lifeblood of an agent. They are simple Pydantic models that carry data and direct the workflow.
### Control vs. Display Events
There are two primary categories of events:
- **`ControlEvent`**: These direct the workflow's execution path. Steps **return** `ControlEvent`s to trigger the next
part of the process. The workflow begins with a `StartEvent` and ends when a step returns a `StopEvent`.
- **`DisplayEvent`**: These provide information to a user interface, like showing the agent's "thoughts" or streaming
back a response. They are **emitted** within a step and never affect the agent's logic.
This separation ensures that UI concerns cannot break your core workflow.
```python
@step()
async def example_step(self, event: InputEvent, displayer: EventDisplayer) -> OutputEvent:
# 1. Emit a DisplayEvent to the UI (does not affect workflow)
await displayer.display_thought("Processing the user's request...")
# 2. Return a ControlEvent to advance the workflow
return OutputEvent(result="done")
```
### Defining Custom Events
You'll create custom `ControlEvent`s to pass data between your steps. Simply inherit from `ControlEvent` and add your
Pydantic fields. The most common starting event for a conversational agent is the built-in `UserMessageEvent`.
```python
from swiss_ai_hub.core.events.agent.control.control_event import ControlEvent
# A custom event to carry data from one step to another
class DocumentProcessedEvent(ControlEvent):
document_id: str
summary: str
confidence_score: float
```
## Steps: The Units of Work
A step is an `async` method that performs a single, logical operation. The `@step` decorator registers it with the
dispatcher and configures its behavior.
```python
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.i18n.locale_string import LocaleString
@step(
name=LocaleString(en="Process Document"),
description=LocaleString(en="Extracts text and generates a summary."),
max_executions_per_run=1, # Prevents accidental loops
stop_on_error=True # Halts the workflow if this step fails
)
async def process_document(self, event: DocumentUploadEvent) -> DocumentProcessedEvent:
# Step logic here...
return DocumentProcessedEvent(...)
```
::: warning Step execution isolation
A fresh `Agent` instance is created for **every** step execution. Do not store state on `self` — it will be lost.
Multiple steps for the same run may execute in parallel on different instances. Use events to pass data between steps.
:::
### Step return types
| Return type | Behavior |
| ------------------ | ----------------------------------- |
| `EventA` | Single event published |
| `EventA \| EventB` | One event published (branching) |
| `list[EventA]` | Multiple events published (fan-out) |
| `None` | Side-effect only, no event |
### Internationalized step names
Use `LocaleString` for step names and descriptions that appear in the UI:
```python
from swiss_ai_hub.core.i18n.locale_string import LocaleString
@step(
name=LocaleString(
en="Search Knowledge Base",
de="Wissensdatenbank durchsuchen",
fr="Rechercher dans la base de connaissances",
it="Cerca nella base di conoscenza",
),
description=LocaleString(en="Retrieves relevant documents", ...),
)
async def search(self, event: UserMessageEvent, t: LocaleHandler) -> SearchEvent:
await displayer.display_thought(t("agent.thought.searching"))
...
```
Translation files live in `packages/agent/swiss_ai_hub/agent/i18n/translations/agent//` with one YAML file
per locale (`en.yml`, `de.yml`, etc.). Lookup order: Local agent translations, agent scope, library, English fallback.
## Configuration: Making Agents Reusable
To keep your agent's logic separate from its settings, the SDK uses a strongly-typed configuration system. This allows
you to change an agent's behavior (e.g., switching LLM models) without changing its code.
::: tip UI-Editable Configuration
To make your agent's configuration editable through the Admin UI, see
[Configurable Agent Forms](../8_configurable_agents/). The Form Duality Pattern allows administrators to create and
customize agent profiles without code changes.
:::
### `AgentConfig`: Global Configuration
Define a class that inherits from `AgentConfig` for settings that apply to the entire agent. This object can be injected
into any step.
```python
from swiss_ai_hub.core.agents.agent_config import AgentConfig
from pydantic import Field
from typing import Annotated
class MyAgentConfig(AgentConfig):
model_name: Annotated[str, Field(description="LLM model name")] = "gpt-4o-mini"
temperature: Annotated[float, Field(description="The LLM temperature")] = 0.7
```
### `StepConfig`: Step-Specific Configuration
For complex, reusable steps, you can create dedicated `StepConfig` classes. Nest them inside your main `AgentConfig`,
and the dispatcher will automatically inject only the relevant config into the step that needs it.
::: code-group
```python [Step config definition]
class SummarizeStepConfig(StepConfig):
max_length: int = 500
```
```python [Embed in agent config]
class MyAgentConfig(AgentConfig):
summarize_step_settings: SummarizeStepConfig = SummarizeStepConfig()
```
```python [Use in step]
@step()
async def summarize_text(self, event: TextEvent, config: SummarizeStepConfig):
# The dispatcher injects only the SummarizeStepConfig object
print(f"Max summary length: {config.max_length}")
pass
```
:::
## Dependency Injection: Automatic Parameters
As you've seen, you don't need to manually pass objects like configs or contexts to your steps. The **Agent Dispatcher**
provides them automatically based on the parameter's type hint.
Here are the objects you can have injected:
| Type | Scope | Description |
| -------------------- | ------ | ------------------------------------------------------------------------- |
| `AgentConfig` | Run | Your agent's main configuration object (immutable per run) |
| `StepConfig` | Step | A specific configuration class for a single step |
| `RunContext` | Run | Valkey-backed KV store, cleared on run completion (30-day TTL safety net) |
| `ThreadContext` | Thread | Valkey-backed KV store, persistent across runs (no TTL) |
| `EventDisplayer` | Step | Helper for emitting `DisplayEvent`s to the UI |
| `AgentMemory` | Step | Long-term memory operations (retrieve, store) |
| `LocaleHandler` | Run | Internationalization — call `t("key")` for translated strings |
| `AgentInstanceTopic` | Step | Metadata: `agent_id`, `thread_id`, `run_id`, `display_id` |
This powerful feature keeps your code clean and focused on business logic.
```python
@step()
async def complex_step(
self,
event: InputEvent,
config: MyAgentConfig, # Injected
run_context: RunContext, # Injected
displayer: EventDisplayer # Injected
) -> StopEvent:
# Use the injected objects to perform work
await displayer.display_thought(f"Using model: {config.model_name}")
await run_context.set("processed_items", 1)
return StopEvent(final_message="Done.")
```
## Next Steps
Now that you understand the fundamentals, explore the **[Core Patterns](../2_core_patterns/)** to see how these concepts
are used to build agent workflows.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/2_core_patterns.md
title: "Core Workflow Patterns"
description: ""
---
# Core Workflow Patterns
This page covers the fundamental, reusable patterns for building agent workflows. By combining these building blocks,
you can create sophisticated and robust agents. Each pattern includes a concise code example and an explanation of its
purpose.
For complete, runnable examples, see the `playground/minimal_workflow/` directory.
## Workflow Control Patterns
These patterns define the fundamental flow of execution in an agent, from simple sequences to complex branching and
parallel processing.
### Simple Linear Workflow
A **linear workflow** is the most basic pattern, where steps execute in a direct sequence from a start event to a stop
event.
- **When to use it**: Ideal for simple, sequential tasks like processing a single input to produce a single output.
- **How it works**: One step returns a `ControlEvent` that is consumed by the next step, forming a direct chain.
```mermaid
graph LR
A[UserMessageEvent] --> B(start_step)
B --> C[SimpleEventA]
C --> D(end_step)
D --> E[StopEvent]
```
```python
class SimpleAgent(Agent):
@step()
async def start_step(self, event: UserMessageEvent) -> SimpleEventA:
return SimpleEventA(payload=event.messages[-1].content)
@step()
async def end_step(self, event: SimpleEventA) -> StopEvent:
return StopEvent()
```
### Conditional Workflow (Branching)
A **conditional workflow** creates decision points, allowing the agent to follow different paths based on runtime
conditions.
- **When to use it**: For routing logic, handling different user intents, or classifying data.
- **How it works**: A step's return type hint includes multiple event types (e.g., `EventA | EventB`). The dispatcher
routes the workflow to the step that handles the specific event type that was returned.
```mermaid
graph TD
A[StartEvent] --> B(start_step)
B -->|random > 0.5| C[AboveThresholdEvent]
B -->|random ≤ 0.5| D[BelowThresholdEvent]
C --> E(end_step)
D --> E
E --> F[StopEvent]
```
```python
class ConditionalAgent(Agent):
@step()
async def start_step(self, event: StartEvent) -> AboveThresholdEvent | BelowThresholdEvent:
if random.random() > 0.5:
return AboveThresholdEvent()
return BelowThresholdEvent()
@step()
async def end_step(self, event: AboveThresholdEvent | BelowThresholdEvent) -> StopEvent:
# This step runs for either outcome of the start_step
return StopEvent()
```
### Bounded Loops (Iteration)
A **looping workflow** executes a step or a series of steps multiple times. It's crucial to ensure loops have a clear
exit condition.
- **When to use it**: Ideal for retry logic, iterative refinement, or processing a batch of items.
- **How it works**: A step returns an event that routes the flow back to an earlier step, using `RunContext` to track
state. Use the `@step(max_executions_per_run=N)` parameter as a safety guard against infinite loops.
```mermaid
graph TD
A[UserMessageEvent] --> B(start_step)
B --> C[BeginEvent]
C --> D(process_a_step)
D --> E[BoundedLoopAEvent]
E --> F(decision_step)
F -->|count < max| C
F -->|count ≥ max| G[DecisionEvent]
G --> H(end_step)
H --> I[StopEvent]
```
```python
class BoundedLoopAgent(Agent):
@step()
async def start_step(self, event: UserMessageEvent, run_context: RunContext) -> BeginEvent:
print("[SimpleAgent.start_step]")
await run_context.set("loop_count", 0)
return BeginEvent(count=0)
@step()
async def process_a_step(self, event: BeginEvent) -> BoundedLoopAEvent:
print("[BoundedLoopAgent.process_a_step]")
return BoundedLoopAEvent()
@step()
async def decision_step(
self, event: BoundedLoopAEvent, agent_config: BoundedLoopAgentConfig, run_context: RunContext
) -> DecisionEvent | BeginEvent:
loop_count = await run_context.get("loop_count")
print("[BoundedLoopAgent.decision_step]", loop_count)
if loop_count < agent_config.loop_max:
await run_context.set("loop_count", loop_count + 1)
return BeginEvent(count=loop_count + 1)
return DecisionEvent()
@step()
async def end_step(self, event: DecisionEvent) -> StopEvent:
print("[SimpleAgent.end_step]")
return StopEvent()
```
### Fan-Out / Fan-In (Parallel Processing)
This powerful pattern allows an agent to split a task into multiple parallel branches (**fan-out**) and then aggregate
the results (**fan-in**).
- **When to use it**: For processing multiple documents, making parallel API calls, or any task that can be broken down
into independent sub-tasks.
- **How it works**:
- **Fan-Out**: A step returns a `list` of events. The dispatcher then triggers the next step *once for each event in
the list*, creating parallel execution branches.
- **Fan-In**: A later step uses a **precondition** to wait until all parallel branches have produced their result
events before it runs.
#### Fixed Number of Events
Use `FixedList({event}, {number_of_events})` to specify a fixed number of expected events:
```mermaid
graph TD
A[StartEvent] --> B(start_step)
B --> C1[FanOutA #1]
B --> C2[FanOutA #2]
B --> C3[FanOutA #N]
C1 --> D1(process_a)
C2 --> D2(process_a)
C3 --> D3(process_a)
D1 --> E1[FanOutB #1]
D2 --> E2[FanOutB #2]
D3 --> E3[FanOutB #N]
E1 --> F(stop_step)
E2 --> F
E3 --> F
F --> G[StopEvent]
```
```python
N = 5
class FanOutAgent(Agent):
@step()
async def start_step(self, _: StartEvent) -> list[FanOutA]:
print("[start_step]")
return [FanOutA(payload=str(i)) for i in range(N)]
@step()
async def process_a(self, event: FanOutA) -> FanOutB:
return FanOutB(payload=event.payload)
@step()
async def stop_step(self, _: FixedList(FanOutB, N)) -> StopEvent:
print("[stop_step]")
return StopEvent()
```
#### Using Preconditions
For dynamic fan-in scenarios, use a precondition function to check if all results are ready:
```mermaid
graph TD
A[StartEvent] --> B(fan_out_step)
B --> C1[ParallelEvent #1]
B --> C2[ParallelEvent #2]
B --> C3[ParallelEvent #N]
C1 --> D1(process_in_parallel)
C2 --> D2(process_in_parallel)
C3 --> D3(process_in_parallel)
D1 --> E1[ResultEvent #1]
D2 --> E2[ResultEvent #2]
D3 --> E3[ResultEvent #N]
E1 --> F{Precondition:
All events ready?}
E2 --> F
E3 --> F
F -->|Yes| G(fan_in_step)
G --> H[StopEvent]
```
```python
# The precondition function checks if all results are ready
@precondition()
async def ensure_enough_events(events: list[ParallelEvent], config: PreconditionAgentConfig) -> bool:
return len(events) == config.number_of_events
class ParallelProcessingAgent(Agent):
@step()
async def fan_out_step(self, _: StartEvent, config: PreconditionAgentConfig) -> list[ParallelEvent]:
# 1. Fan-Out: Return a list of events to start parallel branches
return [ParallelEvent(payload=str(i)) for i in range(config.number_of_events)]
@step()
async def process_in_parallel(self, event: ParallelEvent) -> ResultEvent:
# 2. This step runs in parallel for each ParallelEvent
# ... process the event ...
return ResultEvent(...)
@step(precondition=ensure_enough_events)
async def fan_in_step(self, _: list[ResultEvent]) -> StopEvent:
# 3. Fan-In: This step only runs after the precondition is met
# (i.e., all parallel branches have produced a ResultEvent)
return StopEvent()
```
## State and Configuration Patterns
These patterns focus on managing an agent's memory and behavior dynamically.
### Context Management (The Agent's Memory)
The SDK provides injectable **context objects** to store information during and between runs.
- **When to use it**: For tracking progress, remembering user preferences, or passing data between non-sequential steps.
- **How it works**:
- **`RunContext`**: Ephemeral memory for a *single* workflow run. It's created on a `StartEvent` and destroyed on a
`StopEvent`.
- **`ThreadContext`**: Persistent memory for a conversation *thread*. It survives across multiple agent runs.
```mermaid
graph TD
subgraph "Run 1"
A1[CustomStartEvent] --> B1(start_step)
B1 -->|RunContext:
count=1| C1[ContextEvent]
end
subgraph "Run 2"
A2[CustomStartEvent] --> B2(start_step)
B2 -->|RunContext:
count=1
ThreadContext:
count=2| C2[ContextEvent]
end
B1 -.->|ThreadContext persists:
count=1| B2
```
```python
class ContextAgent(Agent):
@step()
async def start_step(self, event: CustomStartEvent, thread_context: ThreadContext, run_context: RunContext) -> ContextEvent:
thread_count = await thread_context.get("count", 0) # Persists across runs
run_count = await run_context.get("count", 0) # Resets each run
await thread_context.set("count", thread_count + 1)
await run_context.set("count", run_count + 1)
return ContextEvent(thread_count=thread_count + 1, run_count=run_count + 1)
```
### Configuration-Driven Behavior
Separate your agent's logic from its settings using `AgentConfig` and `StepConfig` classes.
- **When to use it**: To create reusable agents, manage settings for different environments.
- **How it works**: The dispatcher injects the entire `AgentConfig` or a specific `StepConfig` into your step based on
its type hint.
```mermaid
graph TD
A[StartEvent] --> B(start_step)
B -->|Uses StartStepConfig| C[EventConfiguredA]
C --> D(middle_step)
D -->|Uses ConfiguredAgentConfig| E[EventConfiguredB]
F{{StartStepConfig}} -.->|Injected| B
G{{ConfiguredAgentConfig}} -.->|Injected| D
```
```python
class ConfiguredAgent(Agent):
@step()
async def start_step(self, event: StartEvent, config: StartStepConfig) -> EventConfiguredA:
# Injects only the specific configuration for this step
return EventConfiguredA(payload=config.some_step_value)
@step()
async def middle_step(self, event: EventConfiguredA, config: ConfiguredAgentConfig) -> EventConfiguredB:
# Injects the entire agent's configuration
return EventConfiguredB(payload=config.some_agent_value)
```
## User Interaction and Feedback
This pattern is essential for creating transparent and user-friendly agents.
### Displaying Information
Agents can provide real-time feedback to the user without interrupting the workflow's logic.
- **When to use it**: To show "chain-of-thought" reasoning, provide status updates for long-running tasks, or stream
back partial results.
- **How it works**: Inject the `EventDisplayer` into a step. Use its methods (`display_thought`, `display_chunk`) to
send `DisplayEvent`s to the user interface. These events do not affect the control flow.
```mermaid
graph TD
A[StartEvent] --> B(start_step)
B -.->|ThoughtEvent| C[User Interface]
B -.->|ChunkEvent| C
B --> D[StopEvent]
```
```python
class DisplayingAgent(Agent):
@step()
async def start_step(self, event: StartEvent, displayer: EventDisplayer) -> StopEvent:
await displayer.display_thought("Let me think....")
await displayer.display_chunk("This is a partial result sent to the user.", model_name="gpt-4")
# ... continue processing ...
return StopEvent()
```
## LLM Integration Patterns
These patterns show how to integrate Large Language Models (LLMs) into your agent workflows, including streaming
responses and cost tracking.
### Streaming LLM Responses to Users
Stream LLM responses incrementally to users while automatically tracking token usage and costs.
- **When to use it**: For any agent that needs to provide LLM-generated responses with real-time feedback.
- **How it works**: The `EventDisplayer.display_llm_stream()` method streams the LLM response as chunks to the user
interface while maintaining buffers for content and thinking. It can return either `LLMEvent` or `LLMStopEvent` (which
combines LLM data with StopEvent).
```mermaid
graph TD
A[UserMessageEvent] --> B(start_step)
B --> C((display_llm_stream))
C -.->|Stream| D1[ChunkEvent]
C -.->|Stream| D2[ChunkEvent]
C -.->|Stream| D3[ChunkEvent]
C -.->|Stream| E[ThoughtEvent]
D1 -.-> F[User Interface]
D2 -.-> F
D3 -.-> F
E -.-> F
C --> G[LLMEvent or LLMStopEvent]
G --> H[StopEvent]
```
```python
# Example 1: Basic LLM streaming (returns LLMEvent)
class LlamaIndexAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
agent_config: LlamaIndexAgentConfig,
displayer: EventDisplayer,
) -> LLMEvent:
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
return await displayer.display_llm_stream(
agent_config.llm,
llm,
event.messages
)
@step()
async def stop_step(self, event: LLMEvent) -> StopEvent:
return StopEvent()
# Example 2: Direct to stop (returns LLMStopEvent)
class LLMWrappingAgent(Agent):
@step()
async def start_step(
self,
event: UserMessageEvent,
agent_config: LLMWrappingAgentConfig,
displayer: EventDisplayer,
) -> LLMStopEvent:
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
# as_stop_step=True returns LLMStopEvent directly
return await displayer.display_llm_stream(
agent_config.llm,
llm,
event.messages,
as_stop_step=True
)
```
The `cost_reporting_llm()` context manager wraps the LLM to automatically track token usage and publish cost events when
the context exits.
## Decision Making & Routing Patterns
These patterns enable agents to make intelligent routing decisions by returning different event types from a step based
on LLM analysis or other logic.
### How Conditional Routing Works
A step can return different event types based on runtime decisions. The workflow dispatcher automatically routes to the
correct next step based on the event type returned.
**Key Concept**: Use type hints like `EventA | EventB` to declare multiple possible return types. The dispatcher
automatically routes to steps that handle each event type.
### Example: LLM Guard Check
**Use Case**: Validate if a user's question is appropriate for the agent to answer using example-based classification.
**From**: `RAGAgent.few_shot_guard_step` - validates questions against few-shot examples
```mermaid
graph TD
A[UserMessageEvent] --> B(guard_step)
B --> C{LLM Guard
Check Examples}
C -->|Appropriate| D[AcceptEvent]
C -->|Not Appropriate| E[RejectEvent]
D --> F(answer_step)
E --> G(reject_message_step)
F --> H[LLMStopEvent]
G --> H
```
```python
from swiss_ai_hub.core.events.agent.user.user_message_event import UserMessageEvent
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.workflow.decorators.step import step
# Define your guard events
class AcceptEvent(Event):
reason: str
class RejectEvent(Event):
reason: str
class SimpleGuardAgent(Agent):
@step()
async def guard_step(
self,
event: UserMessageEvent,
agent_config: AgentConfig,
displayer: EventDisplayer,
t: LocaleHandler,
) -> AcceptEvent | RejectEvent:
"""
Use LLM with examples to determine if question is appropriate.
Returns different event types based on LLM decision.
"""
user_query = event.messages[-1].content
# Use LLM structured prediction to make decision
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
guard_result = await few_shot_guard(
llm=llm,
t=t,
user_query=user_query,
examples=agent_config.few_shot_guard_examples,
)
# Branch based on LLM decision
if guard_result.success:
await displayer.display_thought(f"Question accepted: {guard_result.reasoning}")
return AcceptEvent(reason=guard_result.reasoning)
else:
await displayer.display_thought(f"Question rejected: {guard_result.reasoning}")
return RejectEvent(reason=guard_result.reasoning)
@step()
async def answer_step(
self,
event: UserMessageEvent,
_: AcceptEvent, # Only runs when question is accepted
agent_config: AgentConfig,
displayer: EventDisplayer,
) -> LLMStopEvent:
"""
Generate answer - only runs for accepted questions.
"""
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
return await displayer.display_llm_stream(
agent_config.llm,
llm,
event.messages,
as_stop_step=True
)
@step()
async def reject_message_step(
self,
event: UserMessageEvent,
reject_event: RejectEvent, # Only runs when question is rejected
agent_config: AgentConfig,
displayer: EventDisplayer,
t: LocaleHandler,
) -> LLMStopEvent:
"""
Handle rejected questions - only runs for rejected questions.
"""
# Add rejection message to conversation
messages = event.messages + [
ChatMessage(
role=MessageRole.SYSTEM,
content=t("agent.prompt.guard.reject", reason=reject_event.reason)
)
]
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
return await displayer.display_llm_stream(
agent_config.llm,
llm,
messages,
as_stop_step=True
)
```
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/3_human_in_the_loop.md
title: "Human in the loop"
description: ""
---
# Human in the Loop
The **Human in the Loop (HITL)** pattern allows an agent to pause its execution at a critical point and request input,
approval, or guidance from a human user before continuing.
## How it works
The HITL pattern is orchestrated by a pair of events that manage the pause and resume logic:
1. **Request**: A step in your agent returns a `HumanInTheLoop.request` event. This is a special `ControlEvent` that
also acts as a `DisplayEvent`, pausing the workflow and presenting a question to the user in the UI.
2. **Response**: The user's answer is sent back to the system as a `HumanInTheLoop.response` event.
3. **Resume**: Another step in your agent is configured to accept this response event. When the event arrives, the
dispatcher routes it to the correct step, and the workflow resumes its execution.
The `HumanInTheLoop` helper class simplifies this process by providing a convenient `invoke` method to create the
request event with the correct routing information.
## Three HITL types
The framework provides three interaction types, each rendered differently in the UI:
| Type | Class | UI behavior | Response type |
| ---------------- | ---------------------------- | --------------------------------------------------------------- | ------------- |
| **Input** | `HumanInTheLoopInput` | Popup dialog for free-form text entry | `str` |
| **Confirmation** | `HumanInTheLoopConfirmation` | Yes/No button selection | `bool` |
| **Chat** | `HumanInTheLoopChat` | Message in chat stream (fallback for UIs without popup support) | `str` |
```python
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_input import HumanInTheLoopInput
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_confirmation import HumanInTheLoopConfirmation
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_chat import HumanInTheLoopChat
# Popup with text input field
HumanInTheLoopInput.invoke(question="What is your preferred language?")
# Yes/No buttons
HumanInTheLoopConfirmation.invoke(question="Approve this transaction?")
# Chat message (no special UI treatment)
HumanInTheLoopChat.invoke(question="Please provide additional context.")
```
### Type selection guide
| Use case | Type | Example |
| ----------------------- | ---------------------------- | -------------------------------------------- |
| Free-form user input | `HumanInTheLoopInput` | "Enter search query:", "Describe the issue:" |
| Binary decision | `HumanInTheLoopConfirmation` | "Delete this file?", "Proceed with payment?" |
| Conversational fallback | `HumanInTheLoopChat` | APIs or UIs without popup support |
## Core pattern: single approval
This example shows a simple workflow where the agent asks for a single confirmation before proceeding.
**Reference**: `playground/minimal_workflow/human_in_the_loop_workflow/`
```python
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_input import HumanInTheLoopInput
class ApprovalAgent(Agent):
@step()
async def request_approval(self, event: StartEvent) -> HumanInTheLoopInput.request:
return HumanInTheLoopInput.invoke(question="Please enter your feedback:")
@step()
async def handle_response(self, event: HumanInTheLoopInput.response) -> StopEvent:
user_response = event.response
return StopEvent()
```
## Multi-step approval with custom event pairs
For workflows requiring multiple human interactions, create distinct subclasses. The dispatcher differentiates steps by
event type — using the same base type for multiple interactions causes ambiguity.
**Reference**: `playground/minimal_workflow/multistep_human_in_the_loop_workflow/`
### Step 1: Define custom HITL event pairs
Each HITL interaction point needs its own request/response event pair and a wrapper class:
::: code-group
```python [events/FirstStepHumanInTheLoop.py]
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_input import HumanInTheLoopInput
from swiss_ai_hub.core.events.agent.hitl.request.human_in_the_loop_input_request_event import HumanInTheLoopInputRequestEvent
from swiss_ai_hub.core.events.agent.hitl.response.human_in_the_loop_input_response_event import HumanInTheLoopInputResponseEvent
class FirstStepHumanInTheLoopRequestEvent(HumanInTheLoopInputRequestEvent):
pass
class FirstStepHumanInTheLoopResponseEvent(HumanInTheLoopInputResponseEvent):
pass
class FirstStepHumanInTheLoop(HumanInTheLoopInput):
request = FirstStepHumanInTheLoopRequestEvent
response = FirstStepHumanInTheLoopResponseEvent
```
```python [events/SecondStepHumanInTheLoop.py]
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_input import HumanInTheLoopInput
from swiss_ai_hub.core.events.agent.hitl.request.human_in_the_loop_input_request_event import HumanInTheLoopInputRequestEvent
from swiss_ai_hub.core.events.agent.hitl.response.human_in_the_loop_input_response_event import HumanInTheLoopInputResponseEvent
class SecondStepHumanInTheLoopRequestEvent(HumanInTheLoopInputRequestEvent):
pass
class SecondStepHumanInTheLoopResponseEvent(HumanInTheLoopInputResponseEvent):
pass
class SecondStepHumanInTheLoop(HumanInTheLoopInput):
request = SecondStepHumanInTheLoopRequestEvent
response = SecondStepHumanInTheLoopResponseEvent
```
:::
### Step 2: Use distinct types in the workflow
```python
from swiss_ai_hub.core.events.agent.control.start.start_event import StartEvent
from swiss_ai_hub.core.events.agent.control.stop.stop_event import StopEvent
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.workflow.decorators.step import step
from .events.FirstStepHumanInTheLoop import FirstStepHumanInTheLoop
from .events.SecondStepHumanInTheLoop import SecondStepHumanInTheLoop
class MultistepHumanInTheLoopAgent(Agent):
@step()
async def start_step(self, event: StartEvent) -> FirstStepHumanInTheLoop.request:
return FirstStepHumanInTheLoop.invoke(question="Shall I continue?")
@step()
async def second_hitl(
self, event: FirstStepHumanInTheLoop.response
) -> SecondStepHumanInTheLoop.request:
print(f"First response: {event.response}")
return SecondStepHumanInTheLoop.invoke(question="Are you sure?")
@step()
async def end_step(self, event: SecondStepHumanInTheLoop.response) -> StopEvent:
print(f"Second response: {event.response}")
return StopEvent()
```
## Dynamic HITL type selection
When the HITL type depends on runtime conditions, use union return types:
```python
from swiss_ai_hub.core.events.agent.control.stop.stop_event import StopEvent
from swiss_ai_hub.core.events.agent.user.user_message_event import UserMessageEvent
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_chat import HumanInTheLoopChat
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_confirmation import HumanInTheLoopConfirmation
from swiss_ai_hub.core.events.agent.hitl.human_in_the_loop_input import HumanInTheLoopInput
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.workflow.decorators.step import step
class HitlDemoAgent(Agent):
@step()
async def select_hitl_type(
self, event: UserMessageEvent
) -> HumanInTheLoopInput.request | HumanInTheLoopConfirmation.request | HumanInTheLoopChat.request:
choice = event.user_query.lower()
if "confirmation" in choice:
return HumanInTheLoopConfirmation.invoke("Do you confirm this action?")
elif "chat" in choice:
return HumanInTheLoopChat.invoke("This is a chat-style question. What is your response?")
else:
return HumanInTheLoopInput.invoke("Please enter your text input:")
@step()
async def handle_response(
self,
event: HumanInTheLoopInput.response | HumanInTheLoopConfirmation.response | HumanInTheLoopChat.response,
) -> StopEvent:
if isinstance(event, HumanInTheLoopConfirmation.response):
result = f"Confirmation: {'Yes' if event.response else 'No'}"
else:
result = f"Response: {event.response}"
return StopEvent()
```
## Bot-in-the-Loop (Teams/Slack integration)
Bot-in-the-Loop (BITL) enables workflows to interact with external messaging platforms via the Azure Bot Framework.
Unlike HITL (which prompts users within the agent UI), BITL sends messages to Microsoft Teams channels or Slack channels
and awaits responses from users on those platforms.
### Channel configuration
BITL requires platform-specific configuration:
::: code-group
```python [Microsoft Teams]
from swiss_ai_hub.core.events.agent.bitl.request.bot_in_the_loop_request_event import TeamsConfig
teams_config = TeamsConfig(
channel_id="19:abc123@thread.tacv2", # Teams channel ID
tenant_id="12345678-1234-1234-1234-123456789abc", # Azure AD tenant ID
bot_id="87654321-4321-4321-4321-cba987654321", # Bot UUID
)
```
```python [Slack]
from swiss_ai_hub.core.events.agent.bitl.request.bot_in_the_loop_request_event import SlackConfig
slack_config = SlackConfig(
channel_id="C0123456789", # Slack channel ID (starts with 'C')
service_url="https://slack.botframework.com",
)
```
:::
### Basic usage
```python
from swiss_ai_hub.core.events.agent.control.stop.stop_event import StopEvent
from swiss_ai_hub.core.events.agent.bitl.bot_in_the_loop import BotInTheLoop
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.workflow.decorators.step import step
class BotInTheLoopAgent(Agent):
@step()
async def request_approval(
self, start_event: MyStartEvent
) -> BotInTheLoop.request:
return BotInTheLoop.invoke(
user=start_event.user,
question="Should the agent proceed with the deployment?",
channel_config=start_event.channel_config, # TeamsConfig or SlackConfig
)
@step()
async def handle_response(self, event: BotInTheLoop.response) -> StopEvent:
answer = event.response
if event.responder:
print(f"Answered by: {event.responder.user_name} ({event.responder.user_id})")
if event.responder.aad_object_id: # Teams-specific
print(f"AAD Object ID: {event.responder.aad_object_id}")
return StopEvent()
```
### Iterative conversations
BITL supports multi-turn conversations by returning another `BotInTheLoop.request`:
```python
@step()
async def handle_response(
self, event: BotInTheLoop.response
) -> BotInTheLoop.request | StopEvent:
if event.response.lower() == "yes":
return StopEvent()
else:
return BotInTheLoop.invoke(
user=event.request_event.user,
question="What about now? Ready to proceed?",
channel_config=event.request_event.channel_config,
)
```
### Response event structure
The `BotInTheLoop.response` event provides:
| Field | Type | Description |
| --------------- | --------------------------- | ------------------------------ |
| `response` | `str` | The user's message text |
| `request_event` | `BotInTheLoopRequestEvent` | Original request (for context) |
| `responder` | `BotInTheLoopResponderInfo` | Who responded |
**Responder information:**
| Field | Type | Description |
| ----------------- | -------------- | ------------------------------- |
| `user_id` | `str` | Platform user ID (Slack/Teams) |
| `user_name` | `str` | Display name |
| `additional_info` | `dict \| None` | Platform-specific metadata |
| `aad_object_id` | `str \| None` | Azure AD object ID (Teams only) |
### BITL vs HITL
| Aspect | HumanInTheLoop | BotInTheLoop |
| --------------------- | ------------------------- | ---------------------------------------------- |
| **Platform** | Agent UI (web/mobile) | Teams / Slack |
| **User context** | Same session user | External channel users |
| **UI options** | Input, Confirmation, Chat | Text message only |
| **Response tracking** | Implicit (same user) | Explicit (`responder` field) |
| **Use case** | In-app approvals | Cross-platform notifications, team escalations |
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/4_multi_agent_systems.md
title: "Multi-Agent Systems"
description: ""
---
# Multi-Agent Systems
Complex problems are often best solved by breaking them into smaller, manageable parts. The **Agent in the Loop (AITL)**
pattern allows you to create multi-agent systems where a primary **Orchestrator** agent can delegate specific tasks to
one or more specialized **Worker** agents.
**When to use it**:
- To create modular, reusable components (e.g., an agent that only summarizes documents).
- To separate concerns (e.g., one agent for data retrieval, another for analysis).
- To build complex chains or parallel workflows that combine the strengths of multiple agents.
## How It Works
The AITL pattern is managed by a trio of events that orchestrate the delegation, execution, and response between agents.
1. **Orchestrator sends a Request**: The Orchestrator agent returns an `AgentInTheLoop.request` event. This event acts
as a package, containing the `start_event` for the Worker and the routing information for the response. This pauses
the Orchestrator's workflow.
2. **Worker executes its task**: The dispatcher delivers the `start_event` to the specified Worker agent. The Worker
runs its own self-contained workflow, completely unaware that it was called by another agent.
3. **Worker completes and responds**: When the Worker finishes, it returns a `StopEvent` (or an `ExceptionEvent` if it
fails). The system automatically wraps this final event into either an `AgentInTheLoop.response` or
`AgentInTheLoop.exception` event.
4. **Orchestrator resumes**: The dispatcher routes the response or exception event back to the Orchestrator, which
resumes its workflow in a separate step designed to handle the result.
The `AgentInTheLoop` helper class simplifies this process by providing a convenient `invoke` method to create the
request event.
______________________________________________________________________
## Core Pattern: Orchestrator and Worker
This example shows an `OrchestratorAgent` that asks a `WorkerAgent` to perform a simple calculation. Notice that the
`WorkerAgent` is just a standard, self-contained agent.
**Reference**: `playground/minimal_workflow/agent_in_the_loop_workflow/`
::: code-group
```python [OrchestratorAgent.py]
from swiss_ai_hub.core.events.agent.aitl.agent_in_the_loop import AgentInTheLoop
class OrchestratorAgent(Agent):
@step()
async def delegate_task(self, event: UserMessageEvent) -> AgentInTheLoop.request:
# 1. Delegate the task to the WorkerAgent
return AgentInTheLoop.invoke(
agent_id="worker_agent",
agent_class="WorkerAgent",
start_event=event # Pass the original event to the worker
)
@step()
async def handle_result(self, response: AgentInTheLoop.response) -> StopEvent:
# 3a. This step runs if the worker succeeds
result = response.stop_event.result
return StopEvent(final_message=f"Worker succeeded with result: {result}")
@step()
async def handle_error(self, response: AgentInTheLoop.exception) -> StopEvent:
# 3b. This step runs if the worker fails
error_message = response.exception_event.message
return StopEvent(final_message=f"Worker failed: {error_message}")
```
```python [WorkerAgent.py]
class WorkerAgent(Agent):
@step()
async def process_number(self, event: UserMessageEvent) -> ExtractNumberEvent:
# 2. The worker agent performs its logic...
number = int(event.messages[-1].content)
return ExtractNumberEvent(number=number)
@step()
async def calculate_result(self, event: ExtractNumberEvent) -> WorkerStopEvent:
# ...and returns its own custom StopEvent with a result.
return WorkerStopEvent(result=event.number * 2)
```
:::
## Context Sharing
You can control which contexts are shared from the Orchestrator to the Worker. This is useful for maintaining a
consistent conversation or UI experience.
- `share_thread_id=True` (Default): The Worker shares the same conversation memory (`ThreadContext`) as the
Orchestrator.
- `share_display_id=True` (Default): The Worker's `DisplayEvent`s appear in the same UI stream as the Orchestrator's.
- `share_run_id=False` (Default): The Worker executes in its own independent run.
```python
AgentInTheLoop.invoke(
agent_id="specialized_agent",
agent_class="SpecializedAgent",
start_event=event,
share_thread_id=True, # Share conversation memory
share_display_id=True, # Share UI context
share_run_id=False # Recommended: Keep runs separate
)
```
::: warning
Sharing the `run_id` is an advanced feature and can lead to unexpected behavior, as both agents would be writing to the
same ephemeral `RunContext`. It is almost always better to keep it `False`.
:::
## Common Multi-Agent Patterns
### Specialized Processing (Router)
An orchestrator acts as a router, delegating tasks to different worker agents based on the input.
```python
class DocumentRouterAgent(Agent):
@step()
async def route_document(self, event: DocumentEvent) -> AgentInTheLoop.request:
if event.document_type == "financial":
# Delegate to the financial analysis agent
return AgentInTheLoop.invoke(agent_id="financial_analyzer", ...)
elif event.document_type == "legal":
# Delegate to the legal analysis agent
return AgentInTheLoop.invoke(agent_id="legal_analyzer", ...)
```
### Sequential Agent Chain
A workflow where the output of one worker agent becomes the input for the next, creating a processing pipeline.
```python
class ProcessingChainAgent(Agent):
@step()
async def extract_data(self, event: UserMessageEvent) -> AgentInTheLoop.request:
# First agent in the chain
return AgentInTheLoop.invoke(agent_id="data_extractor", ...)
@step()
async def validate_data(self, response: AgentInTheLoop.response) -> AgentInTheLoop.request:
# The result from the first agent is used to start the second
extracted_data = response.stop_event.result
validation_event = ProcessingEvent(data=extracted_data)
return AgentInTheLoop.invoke(agent_id="data_validator", start_event=validation_event)
```
### Parallel Agent Execution (Fan-Out)
An orchestrator delegates the same task to multiple agents simultaneously and then aggregates their responses.
```python
class ParallelProcessorAgent(Agent):
@step()
async def fan_out(self, event: UserMessageEvent) -> list[FanOutStartedEvent | AgentInTheLoop.request]:
requests = [
AgentInTheLoop.invoke(agent_class="ProcessorAgent", agent_id=agent_id, start_event=..., share_run_id=False)
for agent_id in ("processor_a", "processor_b")
]
# The marker carries the count the join waits for, and is emitted even when nothing is delegated.
return [FanOutStartedEvent(expected=len(requests)), *requests]
@step(precondition=all_delegates_answered)
async def combine_results(
self,
event: FanOutStartedEvent,
answers: list[AgentInTheLoop.response | AgentInTheLoop.exception],
) -> StopEvent:
results = [answer.stop_event.result for answer in answers if answer.is_aitl_response_event]
return StopEvent(final_message=f"Combined results: {results}")
@precondition()
async def all_delegates_answered(
event: FanOutStartedEvent,
answers: list[AgentInTheLoop.response | AgentInTheLoop.exception],
) -> bool:
return len({answer.request_event_id for answer in answers}) >= event.expected
```
Three things about that shape are load-bearing, and each is a bug if you leave it out:
- **A `list[Event]` parameter does not wait.** It re-executes its step on *every* arrival, binding everything received
so far. Without the precondition, `combine_results` fires on the first answer and returns a `StopEvent`, ending the
run before the other delegates have finished. The precondition is the join; the list is only how the events are bound.
- **The count comes from a marker event, not from `FixedList`.** `FixedList(T, N)` bakes `N` into a class at import
time, so it cannot express a count only known at runtime. Count *distinct* `request_event_id`s and compare with `>=` —
JetStream delivery is at-least-once, so a redelivered answer is an ordinary event, and `==` over a raw count would
overshoot and wedge the run at the point it was supposed to finish.
- **Responses and exceptions arrive on one parameter.** A delegate that fails produces an
`AgentInTheLoopExceptionEvent`, never a response, so a join that waits only on responses waits forever as soon as one
delegate fails.
Answers are told apart by `request_event_id`, which names the `AgentInTheLoopRequestEvent` each one answers. Without it,
N parallel answers arrive on one topic indistinguishable from one another.
::: warning A delegate that never answers blocks the caller forever
`AgentInTheLoop` waits indefinitely by default. An agent that is offline, or a mistyped `agent_id`, publishes neither a
stop event nor an exception, so the caller's run never resumes — and in a fan-out one silent delegate wedges the whole
batch. Pass `timeout_seconds` when the caller cannot tolerate that; it synthesises the exception on expiry. It covers a
delegate that does not answer, not a caller that restarts: the timer and the response subscription both live in the
caller's dispatcher process.
Keep `share_run_id=False` (the default) in a fan-out. The response subscription is keyed by the delegated run id, so
sharing it makes every subscriber fire on every delegate's answer.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/5_memory.md
title: "Agent Memory"
description: ""
---
# Agent memory
Agent memory enables long-term personalization and organizational knowledge sharing beyond single-session chat history.
The SDK provides two distinct memory scopes: user memory for private, per-user preferences and organization memory for
shared, tenant-wide facts.
Memory is automatically integrated into agent workflows through dependency injection and dedicated events.
## Two memory scopes
User memory is private to individual users and automatically extracted from conversation messages by the LLM. It stores
personal preferences, working styles, and individual context—things like "User prefers concise code examples in Python."
Retrieval is vector-based semantic search.
Organization memory is shared across all users in a tenant or namespace. Unlike user memory, it requires explicit
documentation rather than automatic inference. It stores company policies, project details, and team conventions—things
like "We deploy to production on Fridays." Retrieval works the same way.
Both scopes retrieve from the vector store only. `MemorySearchResult.relations` still exists on the model but is always
empty.
## Memory in chat agents
Chat blueprints do not write the steps below. They call the `Memory` capability: `Memory.recall(query)` returns a block
per scope that `Conversation.compose` merges into the model's system instructions, and `Memory.remember(...)` returns
the storage request, which belongs ahead of the completion in the list the answering step returns so it is published
before the run ends. See [Capabilities and Tools](../12_capabilities_and_tools/). The explicit workflow below is the
pattern for a custom agent that wires memory by hand.
## Memory workflow pattern
Both memory types follow a common four-step workflow:
```mermaid
graph LR
A[UserMessageEvent] --> B(1. Retrieve Memory)
B --> C[RetrieveMemoryEvent]
C --> D(2. Extend Chat History)
D --> E[AddMemoryToChatHistoryEvent]
E --> F(3. Generate Response)
F --> G[LLMEvent]
G --> H(4. Store Memory)
H --> I[StoreMemoryEvent]
I --> J[StopEvent]
```
The pattern retrieves relevant memories, injects them into the chat history as a system message, generates a
memory-aware response, and persists new learnings.
## User memory pattern
User memory learns personal preferences automatically from conversations. The agent extracts facts about the user's
working style without requiring explicit documentation. Use this pattern for conversational agents that should adapt to
individual user preferences over time—code assistants, personal productivity agents, custom assistants.
Reference implementation: `playground/minimal_workflow/user_memory_workflow/`
::: warning The termination constraint
If memory persistence depends on LLM output, the LLM step **must not** return `StopEvent`. When `as_stop_step=True`, the
workflow terminates immediately with an `LLMStopEvent` — the memory storage step never executes.
```python
# INCORRECT: LLMStopEvent terminates before storage
@step()
async def respond(self, ..., displayer: EventDisplayer) -> LLMStopEvent:
return await displayer.display_llm_stream(..., as_stop_step=True) # Workflow ends here
@step()
async def store(self, llm: LLMStopEvent, ...) -> StoreMemoryEvent:
... # Never executes — StopEvent already terminated the run
# CORRECT: LLMEvent allows downstream steps
@step()
async def respond(self, ..., displayer: EventDisplayer) -> LLMEvent:
return await displayer.display_llm_stream(..., as_stop_step=False)
@step()
async def store(self, llm: LLMEvent, memory: AgentMemory) -> StoreUserMemoryEvent:
await memory.add_user_memory(messages=llm.chat_messages, ...)
return StoreUserMemoryEvent(...)
@step()
async def stop_step(self, _: StoreUserMemoryEvent) -> StopEvent:
return StopEvent()
```
See [The dangling stop violation](../9_execution_model/#the-dangling-stop-violation) for the general rule.
:::
### Complete example
::: code-group
```python [UserMemoryAgent.py]
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.generative_ai.memory.agent_memory import AgentMemory
from swiss_ai_hub.core.generative_ai.chat_history.extend_chat_history_with_user_memory import (
extend_chat_history_with_user_memory,
)
from swiss_ai_hub.core.events.agent.user.user_message_event import UserMessageEvent
from swiss_ai_hub.core.events.agent.semantic.llm.llm_event import LLMEvent
from swiss_ai_hub.core.events.agent.control.stop.stop_event import StopEvent
from swiss_ai_hub.core.events.agent.memory.retrieve.retrieve_user_memory_event import RetrieveUserMemoryEvent
from swiss_ai_hub.core.events.agent.memory.history.add_user_memory_to_chat_history_event import AddUserMemoryToChatHistoryEvent
from swiss_ai_hub.core.events.agent.memory.store.store_user_memory_event import StoreUserMemoryEvent
from swiss_ai_hub.core.topics.agents.agent_instance_topic import AgentInstanceTopic
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
from swiss_ai_hub.core.i18n.locale_handler import LocaleHandler
class UserMemoryAgent(Agent):
"""
Memory-enhanced conversational agent that retrieves and persists user memories.
Use this agent for personalized conversations requiring long-term context
beyond single session chat history.
"""
@step()
async def retrieve_memory_step(
self,
event: UserMessageEvent,
memory: AgentMemory,
) -> RetrieveUserMemoryEvent:
"""Searches user memories to provide personalized context."""
memory_search_result = await memory.search_user_memory(
query=event.user_query,
user_id=event.user.id
)
return RetrieveUserMemoryEvent.from_memory_search_result(
memory_search_result=memory_search_result
)
@step()
async def add_memory_to_chat_history_step(
self,
user_message_event: UserMessageEvent,
memory_event: RetrieveUserMemoryEvent,
t: LocaleHandler
) -> AddUserMemoryToChatHistoryEvent:
"""Prepends memories as system message to guide LLM responses."""
extended_chat_history = extend_chat_history_with_user_memory(
chat_history=user_message_event.messages,
memories=memory_event.memories,
relations=memory_event.relations,
user=user_message_event.user,
t=t,
)
return AddUserMemoryToChatHistoryEvent(extended_history=extended_chat_history)
@step()
async def respond_with_memory_step(
self,
event: AddUserMemoryToChatHistoryEvent,
agent_config: UserMemoryAgentConfig,
displayer: EventDisplayer,
) -> LLMEvent:
"""Generates response using memory-enhanced chat history."""
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
return await displayer.display_llm_stream(
agent_config.llm,
llm,
event.extended_history,
as_stop_step=False
)
@step()
async def update_memory_step(
self,
user_message_event: UserMessageEvent,
llm_event: LLMEvent,
memory: AgentMemory,
topic: AgentInstanceTopic,
) -> StoreUserMemoryEvent:
"""Persists conversation learnings to long-term memory."""
memory_added = await memory.add_user_memory(
messages=llm_event.chat_messages,
user_id=user_message_event.user.id,
thread_id=topic.thread_id,
display_id=topic.display_id,
run_id=topic.run_id,
)
return StoreUserMemoryEvent.from_memory_added_object(
memory_added=memory_added
)
@step()
async def stop_step(self, _: StoreUserMemoryEvent) -> StopEvent:
"""Marks workflow completion."""
return StopEvent()
```
```python [UserMemoryAgentConfig.py]
from swiss_ai_hub.core.agents.agent_config import AgentConfig
from swiss_ai_hub.core.generative_ai.resources.models.llm.llm_config import LLMConfig
class UserMemoryAgentConfig(AgentConfig):
llm: LLMConfig
```
:::
### Key components
#### AgentMemory injection
The `AgentMemory` object is automatically injected into steps via dependency injection:
```python
@step()
async def retrieve_memory_step(
self,
event: UserMessageEvent,
memory: AgentMemory, # Injected automatically
) -> RetrieveUserMemoryEvent:
memory_search_result = await memory.search_user_memory(
query=event.user_query,
user_id=event.user.id
)
return RetrieveUserMemoryEvent.from_memory_search_result(
memory_search_result=memory_search_result
)
```
#### Memory retrieval
`search_user_memory()` performs semantic search against the user's private memory store. It takes the search query
(typically the user's current message), the user ID, and an optional limit (default: 100). It returns a
`MemorySearchResult` containing the matching memories.
#### Chat history extension
The `extend_chat_history_with_user_memory()` helper inserts memories as a system message:
```python
extended_chat_history = extend_chat_history_with_user_memory(
chat_history=user_message_event.messages,
memories=memory_event.memories,
relations=memory_event.relations,
user=user_message_event.user,
t=t, # LocaleHandler for i18n
)
```
LLMs treat system messages as authoritative background information, so memories are presented as optional context that
the LLM may or may not use based on relevance. The memories are inserted after existing system messages (agent
personality/behavior) but before user messages.
#### Memory persistence
`add_user_memory()` uses an LLM to extract learnings from the conversation:
```python
memory_added = await memory.add_user_memory(
messages=llm_event.chat_messages, # Full conversation including LLM response
user_id=user_message_event.user.id,
thread_id=topic.thread_id, # Swiss AI Agent Protocol context
display_id=topic.display_id, # Swiss AI Agent Protocol context
run_id=topic.run_id, # Swiss AI Agent Protocol context
)
```
The LLM analyzes the conversation and extracts facts like "User prefers Python over JavaScript" without storing the
entire conversation.
## Organization memory pattern
Organization memory stores explicit, shared organizational knowledge. Unlike user memory (which is inferred),
organization memory requires users to intentionally document facts. Use this pattern for agents managing shared
organizational context—team conventions, project documentation, company policies, or technical facts that all users
should know.
Reference implementation: `playground/minimal_workflow/organization_memory_workflow/`
### Complete example
::: code-group
```python [OrganizationMemoryAgent.py]
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.generative_ai.memory.agent_memory import AgentMemory
from swiss_ai_hub.core.generative_ai.chat_history.extend_chat_history_with_organization_memory import (
extend_chat_history_with_organization_memory,
)
from swiss_ai_hub.core.events.agent.user.user_message_event import UserMessageEvent
from swiss_ai_hub.core.events.agent.semantic.llm.llm_stop_event import LLMStopEvent
from swiss_ai_hub.core.events.agent.memory.store.store_organization_memory_event import StoreOrganizationMemoryEvent
from swiss_ai_hub.core.events.agent.memory.retrieve.retrieve_organization_memory_event import RetrieveOrganizationMemoryEvent
from swiss_ai_hub.core.events.agent.memory.history.add_organization_memory_to_chat_history_event import AddOrganizationMemoryToChatHistoryEvent
from swiss_ai_hub.core.topics.agents.agent_instance_topic import AgentInstanceTopic
from swiss_ai_hub.core.displayers.event_displayer import EventDisplayer
from swiss_ai_hub.core.i18n.locale_handler import LocaleHandler
class OrganizationMemoryAgent(Agent):
"""
Organization memory management agent that stores and retrieves
explicit organizational facts.
Key Differences from UserMemoryAgent:
- Input: Explicit facts (user provides clean memory text) vs. inferred from chat
- Scope: Organization-wide (shared) vs. user-private
- Namespace: Supports department-level scoping via tenant_namespace
"""
@step()
async def store_organization_memory_step(
self,
event: UserMessageEvent,
memory: AgentMemory,
topic: AgentInstanceTopic,
agent_config: OrganizationMemoryAgentConfig,
) -> StoreOrganizationMemoryEvent:
"""Stores the user's query as an explicit organizational fact."""
memory_added = await memory.add_organization_memory(
memory=event.user_query, # Direct storage - user query is the fact itself
user_id=event.user.id,
thread_id=topic.thread_id,
display_id=topic.display_id,
run_id=topic.run_id,
tenant_id=agent_config.tenant_id,
tenant_namespace=agent_config.tenant_namespace,
)
return StoreOrganizationMemoryEvent.from_memory_added_object(
memory_added=memory_added
)
@step()
async def retrieve_organization_memory_step(
self,
event: UserMessageEvent,
memory: AgentMemory,
agent_config: OrganizationMemoryAgentConfig,
) -> RetrieveOrganizationMemoryEvent:
"""Searches organization memories to provide shared org context."""
memory_search_result = await memory.search_organization_memory(
query=event.user_query,
tenant_id=agent_config.tenant_id,
tenant_namespace=agent_config.tenant_namespace,
user_id=event.user.id,
)
return RetrieveOrganizationMemoryEvent.from_memory_search_result(
memory_search_result=memory_search_result
)
@step()
async def add_memory_to_chat_history_step(
self,
user_message_event: UserMessageEvent,
memory_event: RetrieveOrganizationMemoryEvent,
t: LocaleHandler
) -> AddOrganizationMemoryToChatHistoryEvent:
"""Prepends organization memories as system message."""
extended_chat_history = extend_chat_history_with_organization_memory(
chat_history=user_message_event.messages,
memories=memory_event.memories,
t=t,
)
return AddOrganizationMemoryToChatHistoryEvent(extended_history=extended_chat_history)
@step()
async def respond_with_memory_step(
self,
event: AddOrganizationMemoryToChatHistoryEvent,
agent_config: OrganizationMemoryAgentConfig,
displayer: EventDisplayer,
) -> LLMStopEvent:
"""Generates response using memory-enhanced chat history."""
async with agent_config.llm.cost_reporting_llm(displayer) as llm:
return await displayer.display_llm_stream(
agent_config.llm,
llm,
event.extended_history,
as_stop_step=True
)
```
```python [OrganizationMemoryAgentConfig.py]
from swiss_ai_hub.core.agents.agent_config import AgentConfig
from swiss_ai_hub.core.generative_ai.resources.models.llm.llm_config import LLMConfig
class OrganizationMemoryAgentConfig(AgentConfig):
"""Configuration for OrganizationMemoryAgent.
Defines the LLM and the tenant context (ID and namespace) for memory scoping.
"""
llm: LLMConfig
tenant_id: str
tenant_namespace: str
```
:::
### Key differences from user memory
#### Explicit storage (no inference)
Organization memory is stored directly as provided by the user:
```python
memory_added = await memory.add_organization_memory(
memory=event.user_query, # Direct - no LLM extraction
# ... context fields ...
)
```
Organization memories affect all users, so explicit documentation ensures accuracy and intentionality. This prevents
accidental policy creation from casual conversation.
#### Tenant scoping
Organization memory supports multi-tenant and department-level isolation:
```python
memory_search_result = await memory.search_organization_memory(
query=event.user_query,
tenant_id=agent_config.tenant_id, # Organization boundary
tenant_namespace=agent_config.tenant_namespace, # Department boundary
user_id=event.user.id,
)
```
The namespace parameter scopes memories to departments. `"Engineering"` might contain technical documentation and
deployment procedures, `"Sales"` might contain product pricing and customer segments, and `None` indicates global tenant
knowledge.
#### Shared visibility
Retrieved memories are visible to all users in the tenant/namespace, not just the user who created them.
## Memory events
The memory system provides six specialized events for workflow control:
| Event type | Purpose |
| ----------------------------------------- | ----------------------------------------------- |
| `RetrieveUserMemoryEvent` | Contains retrieved user memories |
| `RetrieveOrganizationMemoryEvent` | Contains retrieved organization memories |
| `AddUserMemoryToChatHistoryEvent` | Contains chat history with user memory injected |
| `AddOrganizationMemoryToChatHistoryEvent` | Contains chat history with org memory injected |
| `StoreUserMemoryEvent` | Confirms user memory persistence |
| `StoreOrganizationMemoryEvent` | Confirms organization memory persistence |
Memory retrieval and storage automatically emit display events for observability. These appear in the Swiss AI Agent
Protocol trace and require no special handling.
## Combining user and organization memory
For agents that need both memory types, combine the workflows:
```python
class HybridMemoryAgent(Agent):
@step()
async def retrieve_user_memory_step(
self, event: UserMessageEvent, memory: AgentMemory
) -> RetrieveUserMemoryEvent:
# Retrieve personal preferences
result = await memory.search_user_memory(query=event.user_query, user_id=event.user.id)
return RetrieveUserMemoryEvent.from_memory_search_result(result)
@step()
async def retrieve_org_memory_step(
self, event: UserMessageEvent, memory: AgentMemory, config: AgentConfig
) -> RetrieveOrganizationMemoryEvent:
# Retrieve organizational facts
result = await memory.search_organization_memory(
query=event.user_query,
tenant_id=config.tenant_id,
tenant_namespace=config.tenant_namespace,
user_id=event.user.id
)
return RetrieveOrganizationMemoryEvent.from_memory_search_result(result)
@step()
async def combine_memories_step(
self,
event: UserMessageEvent,
user_mem: RetrieveUserMemoryEvent,
org_mem: RetrieveOrganizationMemoryEvent,
t: LocaleHandler
) -> CombinedMemoryEvent:
# Extend with both memory types
chat_history = extend_chat_history_with_user_memory(
chat_history=event.messages,
memories=user_mem.memories,
relations=user_mem.relations,
user=event.user,
t=t
)
chat_history = extend_chat_history_with_organization_memory(
chat_history=chat_history, # Already has user memory
memories=org_mem.memories,
t=t
)
return CombinedMemoryEvent(extended_history=chat_history)
```
The order matters: user memories are added first (more general context), then organization memories (specific facts).
## Advanced usage
### Filtering memory retrieval
Narrow memory searches by agent or thread:
```python
@step()
async def retrieve_memory_step(
self, event: UserMessageEvent, memory: AgentMemory, topic: AgentInstanceTopic
) -> RetrieveUserMemoryEvent:
result = await memory.search_user_memory(
query=event.user_query,
user_id=event.user.id,
agent_id=topic.agent_id, # Only memories from this agent
thread_id=topic.thread_id, # Only memories from this conversation
)
return RetrieveUserMemoryEvent.from_memory_search_result(result)
```
Thread-specific filtering supports "recall what we discussed in this conversation" use cases. Agent-specific filtering
keeps a code assistant from seeing memories created by a RAG agent.
### Custom memory extraction
The `AgentMemory` class automatically customizes extraction based on agent class:
```python
class SpecializedMemoryAgent(Agent):
@step()
async def update_memory_step(
self, user_message_event: UserMessageEvent, llm_event: LLMEvent, memory: AgentMemory, topic: AgentInstanceTopic
) -> StoreUserMemoryEvent:
# AgentMemory automatically customizes extraction based on agent class
memory_added = await memory.add_user_memory(
messages=llm_event.chat_messages,
user_id=user_message_event.user.id,
thread_id=topic.thread_id,
display_id=topic.display_id,
run_id=topic.run_id,
)
# AgentMemory includes agent context automatically via self.agent_id
return StoreUserMemoryEvent.from_memory_added_object(memory_added)
```
Code assistants extract technical preferences, RAG agents extract domain interests—all automatically based on agent
type.
### Config-driven memory
Production agents often make memory features optional via configuration flags. Use preconditions to gate memory steps
based on config, preventing race conditions with optional events:
```python
from swiss_ai_hub.agent.workflow.decorators.precondition import precondition
@precondition()
def check_memory_ready(
user_event: UserMessageEvent,
user_memory: RetrieveUserMemoryEvent | None,
org_memory: RetrieveOrganizationMemoryEvent | None,
config: AgentConfig,
) -> bool:
if config.enable_user_memory and user_memory is None:
return False
if config.enable_org_memory and org_memory is None:
return False
return config.enable_user_memory or config.enable_org_memory
@precondition()
def check_storage_complete(
llm: LLMEvent,
stored: StoreUserMemoryEvent | None,
config: AgentConfig,
) -> bool:
if config.enable_memory_storage and stored is None:
return False
return True
```
The `check_memory_ready` precondition blocks the history extension step until all enabled memory types have been
retrieved. The `check_storage_complete` precondition blocks the final stop step until storage completes (if enabled).
This prevents the [optional parameter trap](../9_execution_model/#the-optional-parameter-trap) where steps execute
prematurely with `None` values.
## Observability
All memory operations are automatically traced in the observability dashboard. Retrieval traces show the query, returned
memories, and relevance scores. Storage traces show extracted memories, relationships, and metadata. Chat history
extension displays the system message with memory content.
All memories store full Swiss AI Agent Protocol context: `agent_id` (which agent created the memory), `thread_id` (which
conversation thread), `display_id` (UI display context), `run_id` (workflow execution ID), and `user_id` (who the memory
belongs to or who documented it). This enables complete auditability—you can trace back to which conversation taught the
agent a particular preference.
Storage events also carry `llm_model_name`, the model that extracted the memories, so a stored fact can be attributed to
the model that produced it. It is empty for organization memory, which stores the text you pass verbatim and runs no
model.
## Choosing the extraction model
Extraction and reconciliation are several model calls per stored conversation, and they are short, mechanical tasks — so
they do not need the agent's answer model. `AgentMemory` takes an optional `llm_model_name`; unset, it uses the
platform-wide default (`MEM0_LLM_NAME`). The dispatcher supplies it from the agent config when injecting `AgentMemory`,
reading `AgentConfig.memory_llm_model_name`.
That property is the platform's hook: it returns `None` on the base class, and a blueprint that offers a memory-model
picker overrides it to point at its own field. `RAGAgentConfig` does exactly that, exposing `memory_llm` inside its
`UserMemoryConfig` so administrators pick the model per profile.
```python
class MyAgentConfig(AgentConfig):
memory_llm: Annotated[str | ModelSelect | None, Field(description="Model that extracts memories.")] = None
@property
@override
def memory_llm_model_name(self) -> str | None:
# A form-mode element or a blank submission both mean "use the platform default".
return self.memory_llm if isinstance(self.memory_llm, str) and self.memory_llm else None
```
Only the extraction model is per-agent. Embedding and reranking stay deployment-wide, because memories written with one
embedding model cannot be searched with another.
The RAG blueprints do not write user memory themselves — they delegate it to the `MemoryWriterAgent` so the run
finalizes as soon as the answer is ready. The chosen model travels there on the start event (`origin_memory_llm`), which
is what keeps extraction on the profile's own model rather than the platform default.
## Best practices
Use user memory for preferences ("User prefers brief responses") and organization memory for facts ("We deploy on
Fridays"). Let user memory infer from conversation while explicitly documenting organization memory. Always retrieve
memories at workflow start so memory context guides the entire response, and store new learnings at workflow end after
the LLM response is included.
Memory retrieval adds roughly 100ms latency. Use the `limit` parameter to avoid overwhelming context, and filter by
agent or thread when appropriate to reduce irrelevant memories.
User memory is GDPR-compliant—users can view, edit, and delete all their memories. Organization memory requires access
control since changes affect all users. Every memory tracks who created it and when for auditability, and all memory
data stays on Swiss infrastructure.
::: tip Next steps
Explore the complete examples in `playground/minimal_workflow/user_memory_workflow/` and
`playground/minimal_workflow/organization_memory_workflow/`. Review memory events in Langfuse after running a
memory-enhanced agent. Try building a hybrid agent combining both memory types, or experiment with namespace scoping for
department-level isolation.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/6_testing_and_debugging.md
title: "Testing and debugging"
description: ""
---
# Testing and debugging
Testing and debugging agents needs a different approach than traditional applications because of their event-driven,
asynchronous nature.
## Unit testing: direct step invocation
The simplest way to test individual steps is to instantiate the agent and call step methods directly with mocked
dependencies:
```python
from unittest.mock import AsyncMock, Mock
async def test_retrieve_step():
agent = MyAgent()
event = UserMessageEvent(messages=[...], user=fake_user(), locale="en")
memory = Mock(spec=AgentMemory)
memory.search_user_memory = AsyncMock(return_value=MemorySearchResult(...))
result = await agent.retrieve_step(event, memory)
assert isinstance(result, RetrieveUserMemoryEvent)
memory.search_user_memory.assert_called_once()
```
This approach tests step logic in isolation without the dispatcher, NATS, or any infrastructure. Mock injected
dependencies (`AgentMemory`, `EventDisplayer`, `RunContext`) and assert on the returned event.
## Integration testing: pytest-bdd + AgentTestRunner
Use Behavior-Driven Development (BDD) with `pytest-bdd` for testing full agent workflows.
### Basic test structure
1. **Feature file** - Describe behavior in natural language
```gherkin
# tests/features/iterative_agent.feature
Feature: Iterative Processing Agent
An agent that performs iterative processing with configurable limits
Scenario: Agent processes data with iteration limit
Given an iterative processing agent with maximum 2 iterations
When I ask the agent to process some data
Then the agent should complete all iterations
And the agent should stop after reaching the limit
And the processing should be successful
```
2. **Test implementation** - Connect Gherkin to code
::: code-group
```python [Test setup]
from swiss_ai_hub.core.testing.asyncio_utils.bdd import async_test
from pytest_bdd import given, parsers, scenarios, then, when
from swiss_ai_hub.agent.runners.agent_test_runner import AgentTestRunner
scenarios("./features/iterative_agent.feature")
@given(parsers.parse('an iterative processing agent with maximum {max_iterations:d} iterations'))
def _(max_iterations: int):
return AgentTestRunner(
agent_type=BoundedLoopAgent,
agent_config=BoundedLoopAgentConfig(
agent_id="iterative_agent",
loop_max=max_iterations
)
)
```
```python [Test execution]
@when("I ask the agent to process some data")
@async_test
async def _(agent_runner: AgentTestRunner):
async with agent_runner.test_run() as topic:
await agent_runner.send_event_from_topic(
topic=topic,
start_event=UserMessageEvent(
messages=[ChatMessage(content="Process this data", role=MessageRole.USER)],
user=fake_user()
)
)
@then("the agent should complete all iterations")
def _(agent_runner: AgentTestRunner):
iteration_events = agent_runner.get_events_of_class(BeginEvent)
assert len(iteration_events) == 3, f"Expected 3 iterations, got {len(iteration_events)}"
```
:::
## AgentTestRunner: core testing tool
`AgentTestRunner` provides a sandboxed environment for testing agents.
### Basic usage
```python
async def test_simple_agent():
runner = AgentTestRunner(
agent_type=MyAgent,
agent_config=MyAgentConfig(agent_id="test_agent")
)
async with runner.test_run() as topic:
await runner.send_event_from_topic(
topic=topic,
start_event=UserMessageEvent(...)
)
# Assertions
assert runner.has_stop_event
stop_event = runner.get_stop_event()
assert "expected content" in stop_event.final_message
```
### Event inspection methods
::: details Available methods
```python
# Check for specific events
assert runner.has_start_event
assert runner.has_stop_event
# Get specific events
stop_event = runner.get_stop_event()
start_event = runner.get_start_event()
# Get events by type
all_events = runner.get_events_of_class(MyCustomEvent)
single_event = runner.get_event_of_class(MyCustomEvent)
# Count events
event_count = len(runner.get_events_of_class(ProcessingEvent))
```
:::
## Debugging strategy: trace-driven development
Traditional debugging with breakpoints doesn't work well for event-driven agents. Use trace-driven debugging instead.
> [!TIP] Your debugging toolkit: Langfuse tracing (primary), comprehensive logging, trigger scripts, event flow
> inspection.
### Essential tool: trigger.py scripts
Create `trigger.py` scripts to test specific scenarios:
```python
# my_agent/trigger.py
import asyncio
from swiss_ai_hub.core.infrastructure.logging.logger import enable_logging
from swiss_ai_hub.agent.runners.agent_test_runner import AgentTestRunner
# ALWAYS enable logging for debugging
enable_logging()
async def main():
runner = AgentTestRunner(
agent_type=MyAgent,
agent_config=MyAgentConfig(
agent_id="debug_agent"
)
)
async with runner.test_run() as topic:
await runner.send_event_from_topic(
topic=topic,
start_event=UserMessageEvent(
messages=[ChatMessage(content="test input", role=MessageRole.USER)],
user=fake_user()
)
)
if __name__ == "__main__":
asyncio.run(main())
```
### Interactive testing: run.py scripts
For agents that need to run continuously:
```python
# my_agent/run.py
import asyncio
from swiss_ai_hub.core.infrastructure.logging.logger import enable_logging
from swiss_ai_hub.agent.runners.agent_test_runner import AgentTestRunner
enable_logging()
async def main():
runner = AgentTestRunner(
agent_type=MyAgent,
agent_config=MyAgentConfig(agent_id="interactive_agent")
)
# Keeps agent running for interactive testing
await runner.run_forever()
if __name__ == "__main__":
asyncio.run(main())
```
## Langfuse tracing: visual debugging
Langfuse provides step-by-step visualization of agent execution at `http://localhost:6006`.
**Key features:**
- **Trace view** - See complete workflow execution
- **Step details** - Click steps to inspect inputs/outputs
- **Timing analysis** - Identify performance bottlenecks
- **Error tracking** - Pinpoint where failures occur
**Debugging workflow:**
1. Run your `trigger.py` script
2. Open Langfuse UI at `localhost:6006`
3. Find your agent's execution trace
4. Click through steps to inspect event flow
5. Identify where things go wrong
## Running tests
```bash
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/test_my_agent.py
# Run with verbose output
uv run pytest -v tests/
# Run with coverage
uv run pytest --cov=swiss_ai_hub tests/
```
## Implementation checklist
Use this checklist when building or reviewing agents:
### Before implementation
- [ ] Understand the [execution model](../9_execution_model/) — steps are a dependency graph, not a sequence
- [ ] Review the [memory lifecycle](../5_memory/) if your agent uses memory
- [ ] Study production agents: `packages/agent/swiss_ai_hub/agent/agents/rag_agent/`, `expert_rag_agent/`
### For each step
- [ ] Optional parameters (`T | None = None`) have preconditions checking both config AND event presence
- [ ] Precondition parameter types are a subset of the step's injectable types
- [ ] Return type correctly indicates terminal (`StopEvent`) vs. non-terminal
- [ ] No dependency on `StopEvent` or its subclasses as input parameters
### For memory integration
- [ ] LLM step uses `as_stop_step=False` (returns `LLMEvent`, not `LLMStopEvent`)
- [ ] Storage step depends on `LLMEvent`
- [ ] Final step has a precondition waiting for storage completion
### After implementation
- [ ] Langfuse/Phoenix trace shows expected execution order
- [ ] No duplicate step executions (check for the
[optional parameter trap](../9_execution_model/#the-optional-parameter-trap))
- [ ] No events after `StopEvent`
- [ ] Tests cover all config flag combinations
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/7_production_deployment.md
title: "Production Deployment"
description: ""
---
# Production Deployment
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/8_agent_observation.md
title: "Agent Observation"
description: ""
---
# Agent Observation
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/8_configurable_agents.md
title: "Configurable Agent Forms"
description: ""
---
# Configurable Agent Forms
This guide explains how to define agent configuration forms that allow administrators to create and customize agent
profiles without code changes.
## Overview
The SDK uses the **Form Duality Pattern** where a single Pydantic model serves two purposes:
1. **Form Mode**: Fields contain `FormkitElement` instances that define the UI form
2. **Data Mode**: Fields contain primitive values that hold validated configuration
This pattern ensures the form schema and data model cannot become desynchronized.
## Basic Pattern
### Defining a Configurable AgentConfig
```python
from typing import Annotated
from pydantic import Field
from swiss_ai_hub.core.agents.agent_config import AgentConfig
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.form.constraints import Ge, Le
from swiss_ai_hub.core.form.elements import InputNumber, InputText
class MyAgentConfig(AgentConfig):
# Field with duality: str for data mode, InputText for form mode
model_name: Annotated[
str | InputText,
Field(description="The LLM model to use"),
] = "gpt-4"
# Numeric field with constraints
temperature: Annotated[
float | InputNumber,
Field(description="LLM temperature"),
Ge(0.0), # Minimum value
Le(1.0), # Maximum value
] = 0.7
@classmethod
def as_form(cls) -> "MyAgentConfig":
"""Create form-mode config with FormKit elements for UI rendering."""
base = AgentConfig.as_form()
return cls(
# Inherit base fields from AgentConfig
agent_id=base.agent_id,
name=base.name,
description=base.description,
icon=base.icon,
# Define form elements for custom fields
model_name=InputText(
label=LocaleString(en="Model", de="Modell"),
help=LocaleString(en="Select the LLM model to use"),
),
temperature=InputNumber(
label=LocaleString(en="Temperature", de="Temperatur"),
help=LocaleString(en="Controls response creativity (0=focused, 1=creative)"),
min=0.0,
max=1.0,
step=0.1,
),
)
```
### Registering with AgentRunner
```python
from swiss_ai_hub.agent.runners.agent_runner import AgentRunner
from .MyAgent import MyAgent
from .MyAgentConfig import MyAgentConfig
async def main():
runner = AgentRunner(
agent_type=MyAgent,
agent_config=MyAgentConfig.as_form(), # Form mode!
)
await runner.run_forever()
```
The `as_form()` call creates a config instance in form mode. When the agent registers via discovery, the form schema is
extracted and stored. Administrators can then create profiles through the Admin UI.
## Available FormKit Elements
### Text Input
```python
from swiss_ai_hub.core.form.elements import InputText
system_prompt: Annotated[str | InputText, Field()] = "You are a helpful assistant."
# In as_form():
system_prompt=InputText(
label=LocaleString(en="System Prompt"),
help=LocaleString(en="Instructions for the agent"),
required=True,
)
```
### Numeric Input
```python
from swiss_ai_hub.core.form.elements import InputNumber
from swiss_ai_hub.core.form.constraints import Ge, Le
max_tokens: Annotated[int | InputNumber, Field(), Ge(1), Le(4096)] = 1024
# In as_form():
max_tokens=InputNumber(
label=LocaleString(en="Max Tokens"),
min=1,
max=4096,
step=1,
)
```
### Boolean Toggle
```python
from swiss_ai_hub.core.form.elements import ToggleSwitch
enable_citations: Annotated[bool | ToggleSwitch, Field()] = True
# In as_form():
enable_citations=ToggleSwitch(
label=LocaleString(en="Enable Citations"),
help=LocaleString(en="Include source citations in responses"),
)
```
### Dropdown Selection
```python
from swiss_ai_hub.core.form.elements import Select
response_format: Annotated[str | Select, Field()] = "text"
# In as_form():
response_format=Select(
label=LocaleString(en="Response Format"),
options=[
{"label": "Plain Text", "value": "text"},
{"label": "Markdown", "value": "markdown"},
{"label": "JSON", "value": "json"},
],
)
```
### Multi-Language Input
```python
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.form.elements import LocaleInput
greeting: Annotated[LocaleString | LocaleInput, Field()]
# In as_form():
greeting=LocaleInput(
label=LocaleString(en="Greeting Message"),
help=LocaleString(en="Shown when conversation starts"),
)
```
### Model Selector
```python
from swiss_ai_hub.core.form.elements import ModelSelect
llm_model: Annotated[str | ModelSelect, Field()] = "gpt-4"
# In as_form():
llm_model=ModelSelect(
label=LocaleString(en="Language Model"),
help=LocaleString(en="Select from available models"),
)
```
The `ModelSelect` element automatically populates from the LiteLLM model registry at runtime.
## Form-Safe Constraints
Standard Pydantic constraints (`Field(ge=0, le=1)`) don't work with the duality pattern because they can't validate
FormkitElement types. Use SDK-provided constraints instead:
```python
from swiss_ai_hub.core.form.constraints import Ge, Le, Gt, Lt, MinLen, MaxLen, Pattern
# Numeric constraints
temperature: Annotated[float | InputNumber, Ge(0.0), Le(1.0)] = 0.7
min_score: Annotated[float | InputNumber, Gt(0.0)] = 0.1 # Greater than (exclusive)
# String constraints
api_key: Annotated[str | InputText, MinLen(10), MaxLen(100)] = ""
agent_id: Annotated[str | InputText, Pattern(r"^[a-z0-9_-]+$")] = ""
```
These constraints skip validation when the field contains a FormkitElement, enabling Pydantic validation to work in both
modes.
## Non-Configurable Fields
Some fields should not appear in the form (deployment-specific configuration). Omit the FormKit element alternative:
```python
class MyAgentConfig(AgentConfig):
# Configurable (appears in form) - has FormKit alternative
model_name: Annotated[str | InputText, Field()] = "gpt-4"
# Non-configurable (set at deployment) - no FormKit alternative
channel_config: TeamsConfig
@classmethod
def as_form(cls, channel_config: TeamsConfig) -> "MyAgentConfig":
base = AgentConfig.as_form()
return cls(
agent_id=base.agent_id,
name=base.name,
description=base.description,
icon=base.icon,
model_name=InputText(label=LocaleString(en="Model")),
channel_config=channel_config, # Actual value, not FormKit element
)
```
Non-configurable fields are merged with user-submitted configuration at runtime. The form only shows configurable
fields.
## Nested Forms
Forms can contain other forms using the `Group` element:
```python
from swiss_ai_hub.core.form.form import Form
from swiss_ai_hub.core.form.elements import InputNumber, ModelSelect
class LLMConfig(Form):
"""Nested form for LLM settings."""
model_name: Annotated[str | ModelSelect, Field()] = "gpt-4"
temperature: Annotated[float | InputNumber, Ge(0.0), Le(1.0)] = 0.7
max_tokens: Annotated[int | InputNumber, Ge(1), Le(4096)] = 1024
@classmethod
def as_form(cls) -> "LLMConfig":
return cls(
model_name=ModelSelect(label=LocaleString(en="Model")),
temperature=InputNumber(label=LocaleString(en="Temperature"), min=0.0, max=1.0),
max_tokens=InputNumber(label=LocaleString(en="Max Tokens"), min=1, max=4096),
)
class MyAgentConfig(AgentConfig):
llm: Annotated[LLMConfig, Field(title="LLM Settings")]
@classmethod
def as_form(cls) -> "MyAgentConfig":
base = AgentConfig.as_form()
return cls(
agent_id=base.agent_id,
name=base.name,
description=base.description,
icon=base.icon,
llm=LLMConfig.as_form(), # Nested form
)
```
Nested forms automatically render as collapsible `Group` elements in the UI.
## Repeater Forms (Arrays)
For lists of configurable items, use a list of forms:
```python
class ExampleForm(Form):
"""Single example for few-shot learning."""
input_text: Annotated[str | InputText, Field()] = ""
expected_output: Annotated[str | InputText, Field()] = ""
@classmethod
def as_form(cls) -> "ExampleForm":
return cls(
input_text=InputText(label=LocaleString(en="Input")),
expected_output=InputText(label=LocaleString(en="Expected Output")),
)
class MyAgentConfig(AgentConfig):
examples: Annotated[list[ExampleForm], Field(title="Few-Shot Examples")]
@classmethod
def as_form(cls) -> "MyAgentConfig":
base = AgentConfig.as_form()
return cls(
agent_id=base.agent_id,
name=base.name,
description=base.description,
icon=base.icon,
examples=[ExampleForm.as_form()], # List with one template
)
```
Lists of forms render as `Repeater` elements, allowing users to add/remove items dynamically.
## Accessing Configuration in Steps
The dispatcher injects the validated configuration into step methods via type annotation:
```python
from swiss_ai_hub.agent.workflow.decorators.step import step
class MyAgent(Agent):
@step()
async def process_message(
self,
event: UserMessageEvent,
agent_config: MyAgentConfig, # Injected by dispatcher
) -> ResponseEvent:
# Access configuration values
model = agent_config.model_name
temperature = agent_config.temperature
# Use nested config
max_tokens = agent_config.llm.max_tokens
# ...
```
Configuration is fetched via RPC when the agent receives a `StartEvent`, validated against the Pydantic model, and
cached for the duration of the run.
## Conditional Field Visibility
Show or hide fields based on other field values:
```python
use_custom_prompt: Annotated[bool | ToggleSwitch, Field()] = False
custom_prompt: Annotated[str | InputText, Field()] = ""
# In as_form():
use_custom_prompt=ToggleSwitch(label=LocaleString(en="Use Custom Prompt")),
custom_prompt=InputText(
label=LocaleString(en="Custom Prompt"),
condition_if="$get(use_custom_prompt).value === true", # FormKit condition
)
```
The `condition_if` parameter uses FormKit's expression syntax. The field only appears when the condition evaluates to
true.
## Complete Example
```python
from typing import Annotated
from pydantic import Field
from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.runners.agent_runner import AgentRunner
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.agents.agent_config import AgentConfig
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.events.control.stop_event import StopEvent
from swiss_ai_hub.core.events.control.user_message_event import UserMessageEvent
from swiss_ai_hub.core.form.constraints import Ge, Le
from swiss_ai_hub.core.form.elements import InputNumber, InputText, ModelSelect, ToggleSwitch
class QAAgentConfig(AgentConfig):
"""Configuration for a question-answering agent."""
model_name: Annotated[str | ModelSelect, Field(description="LLM model")] = "gpt-4"
temperature: Annotated[float | InputNumber, Field(), Ge(0.0), Le(1.0)] = 0.3
system_prompt: Annotated[str | InputText, Field()] = "You are a helpful assistant."
enable_citations: Annotated[bool | ToggleSwitch, Field()] = True
@classmethod
def as_form(cls) -> "QAAgentConfig":
base = AgentConfig.as_form()
return cls(
agent_id=base.agent_id,
name=base.name,
description=base.description,
icon=base.icon,
model_name=ModelSelect(
label=LocaleString(en="Model", de="Modell"),
help=LocaleString(en="Language model for generating responses"),
),
temperature=InputNumber(
label=LocaleString(en="Temperature", de="Temperatur"),
help=LocaleString(en="Lower = more focused, Higher = more creative"),
min=0.0,
max=1.0,
step=0.1,
),
system_prompt=InputText(
label=LocaleString(en="System Prompt", de="System-Prompt"),
help=LocaleString(en="Instructions for the assistant"),
),
enable_citations=ToggleSwitch(
label=LocaleString(en="Enable Citations", de="Zitate aktivieren"),
help=LocaleString(en="Include source references in answers"),
),
)
class QAAgent(Agent):
@step()
async def answer_question(
self,
event: UserMessageEvent,
agent_config: QAAgentConfig,
) -> StopEvent:
# Use configuration
model = agent_config.model_name
temp = agent_config.temperature
citations = agent_config.enable_citations
# ... generate answer using config ...
return StopEvent()
async def main():
runner = AgentRunner(
agent_type=QAAgent,
agent_config=QAAgentConfig.as_form(),
)
await runner.run_forever()
```
## Best Practices
### Field Naming
- Use descriptive field names that make sense to administrators
- Provide `help` text explaining what each setting does
- Include reasonable default values
### Validation
- Always use constraints (`Ge`, `Le`, `Pattern`) for numeric and string fields
- Validate early to prevent invalid configurations from being saved
### Localization
- Use `LocaleString` for all user-facing text (labels, help, descriptions)
- Support at least German and English (`de`, `en`)
### Testing
- Test both form mode (`as_form()`) and data mode (with actual values)
- Verify form schema generation with `config.to_formkit_form()`
- Test configuration injection in step methods
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/9_execution_model.md
title: "Execution Model"
description: ""
---
# Execution Model
Understanding the execution model is essential for building correct agents. Most bugs in agent development stem from
misunderstanding how the dispatcher schedules steps. This page explains the mechanics in detail.
## The DAG mental model
An agent is not a sequence of function calls. It is a **Directed Acyclic Graph (DAG)** where nodes are **steps** and
edges are **events**. The workflow topology is implicit, derived entirely from type hints on your `@step` methods.
**Steps declare data requirements, not execution order.**
A step executes when the workflow engine can provide all its required parameters — not when a prior step "calls" it.
This has three critical implications:
1. Any step can depend on any event, including the original `StartEvent`, regardless of how many steps have executed
since
2. Parallel execution is automatic: steps with independent dependencies execute concurrently
3. The workflow is a dependency graph, not a sequence
### The dispatcher's three-phase cycle
The `AgentDispatcher` operates as a reactive state machine:
1. **Ingestion**: Subscribes to the agent's NATS JetStream subject and receives events
2. **Evaluation**: Upon receiving event *e*, identifies all steps *S* where *e* matches an input type of *S*
3. **Trigger**: Invokes step *S* if and only if all required inputs are present in the event store
Execution order is emergent, not prescriptive.
## Data dependency thinking
### Two perspectives
#### Top-down: "What do I have, what do I do next?"
Start with input, proceed toward output. Natural for sequential pipelines but encourages linear chains and unnecessary
data passing.
#### Bottom-up: "What do I need, what provides it?"
Start with desired output, work backward. Naturally identifies all data dependencies and makes direct dependencies on
`StartEvent` obvious.
### The critical difference
**Top-down produces this (problematic):**
```python
@step()
async def retrieve(self, event: UserMessageEvent) -> RetrieveEvent:
nodes = await retriever.retrieve(event.user_query)
return RetrieveEvent(nodes=nodes, user_query=event.user_query) # Passing query forward
@step()
async def respond(self, event: RetrieveEvent) -> StopEvent:
return await generate(event.user_query, event.nodes) # Accessing passed-through data
```
The `user_query` field on `RetrieveEvent` is **pass-through pollution** — the event carries data it doesn't semantically
represent just to shuttle it downstream.
**Bottom-up produces this (correct):**
```python
@step()
async def retrieve(self, event: UserMessageEvent) -> RetrieveEvent:
nodes = await retriever.retrieve(event.user_query)
return RetrieveEvent(nodes=nodes) # Only retrieval-specific data
@step()
async def respond(
self,
retrieve_event: RetrieveEvent,
user_event: UserMessageEvent, # Direct dependency on original event
) -> StopEvent:
return await generate(user_event.user_query, retrieve_event.nodes)
```
Each event contains only the data it semantically represents. The `respond` step declares exactly what it needs: the
retrieved nodes *and* the original user message.
### The unified approach
1. **Sketch top-down** to understand logical flow
2. **Refine bottom-up** to identify true dependencies
3. **Validate** both directions
For each step, ask:
- *Top-down:* What transformation does this step perform?
- *Bottom-up:* What is the minimal set of data this step requires?
## Step execution isolation
::: warning
A fresh `Agent` instance is created for **every** step execution. Do not store state on `self` — it will be lost between
steps. Multiple steps for the same run may execute in parallel on different instances.
:::
Use events to pass data between steps, `RunContext` for loop counters, and `ThreadContext` for cross-run state.
## Execution rules
### The minimum viable input rule
A step executes the moment its minimum required inputs are satisfied.
Given a step *S* with:
- **R(S)** = set of required parameters (no default value)
- **O(S)** = set of optional parameters (typed as `T | None = None`)
- **E** = set of available events
**Trigger condition:** Execution occurs when **R(S) ⊆ E** — all required parameter types are available in the event
store.
- Required parameters: injected from matching events
- Optional parameters: injected if present, otherwise `None`
### The race condition problem
Consider a step with both required and optional event parameters:
```python
@step()
async def merge(self, primary: EventA, secondary: EventB | None = None) -> OutputEvent:
...
```
**Execution sequence:**
| Time | Event arrives | R(S) satisfied | O(S) satisfied | Result |
| ---- | ------------- | -------------- | -------------- | ----------------------------------------------- |
| t1 | EventA | Yes | No | Step executes with `(EventA, None)` |
| t2 | EventB | Yes | Yes | Step executes **again** with `(EventA, EventB)` |
The step executed twice. This is not a bug — it is the defined behavior. The minimum viable input rule triggers
execution as soon as required parameters are satisfied, and again when optional parameters arrive.
::: warning
Do not use optional event parameters to "wait" for data that might arrive later. Use `@precondition` instead.
:::
### The six execution rules
| Rule | Statement | Violation consequence |
| ------ | -------------------------------------------------- | ----------------------------------------------------------- |
| **R1** | Steps execute when R(S) ⊆ E | Race conditions with optional parameters |
| **R2** | Steps may execute multiple times per run | Duplicate processing, wasted resources |
| **R3** | `list[Event]` parameters trigger on each new event | Unexpected re-execution without precondition or `FixedList` |
| **R4** | No events may be published after `StopEvent` | Illegal workflow state |
| **R5** | Preconditions gate execution | Deadlock if precondition never satisfies |
| **R6** | All events persist until run completion | Late steps can access early events |
## Synchronization primitives
### The `@precondition` decorator
A precondition is a gatekeeper function that executes before step logic.
**Mechanics:**
- The dispatcher invokes the precondition using the same dependency injection as steps
- If it returns `False`, the step is not scheduled
- The precondition re-evaluates on each new event arrival
**Critical constraint:** Precondition parameters must be a **subset** of the step's injectable types. Parameter names
are irrelevant; dependency injection resolves by type.
```python
from swiss_ai_hub.agent.workflow.decorators.precondition import precondition
@precondition()
async def check_ready(
evt: EventB | None, # Name differs from step parameter — that's fine
cfg: AgentConfig, # Resolved by type, not name
) -> bool:
if cfg.enable_feature and evt is None:
return False # Wait for optional event
return True
@step(precondition=check_ready)
async def process(
self,
required: EventA,
optional: EventB | None = None, # Type matches precondition's 'evt'
config: AgentConfig, # Type matches precondition's 'cfg'
) -> OutputEvent:
# Guaranteed: if config.enable_feature, optional is not None
...
```
### List parameters
When a step parameter is typed as `list[EventType]`, the step executes on **each new event arrival**:
```python
@step()
async def aggregate(self, results: list[ResultEvent]) -> StopEvent:
# Executes every time a ResultEvent arrives
...
```
**Execution sequence for 3 produced events:**
| Event arrival | List contents | Step executes |
| -------------- | ------------------------------------------------ | ------------- |
| ResultEvent #1 | [ResultEvent #1] | Yes |
| ResultEvent #2 | [ResultEvent #1, ResultEvent #2] | Yes |
| ResultEvent #3 | [ResultEvent #1, ResultEvent #2, ResultEvent #3] | Yes |
The step executes 3 times. This follows from the minimum viable input rule: a list of length 1 satisfies `list[T]`.
Events in the list are ordered by arrival time.
### FixedList for compile-time known counts
When you know the exact number of events at definition time, use `FixedList` to block until all events are available:
```python
from swiss_ai_hub.core.workflow.annotations.custom_types.list_of_size import FixedList
N = 5
@step()
async def aggregate(self, results: FixedList(ResultEvent, N)) -> StopEvent:
# Executes once when exactly N ResultEvents are available
...
```
`FixedList` requires *N* to be known at definition time.
### Precondition with expected count
When the count is determined at runtime (e.g., from configuration), use a precondition:
```python
@precondition()
async def check_all_arrived(results: list[ResultEvent], config: AgentConfig) -> bool:
return len(results) >= config.expected_count
@step(precondition=check_all_arrived)
async def aggregate(self, results: list[ResultEvent], config: AgentConfig) -> StopEvent:
# Executes once when expected count reached
...
```
### Event resolution strategy
When binding events to step parameters:
1. **Fixed collection:** `FixedList(Event, N)` blocks until exactly *N* events are available, then returns all *N*
2. **Unbounded list:** `list[Event]` returns all events of that type currently available; step re-executes on each new
arrival
3. **Single instance:** If the triggering event matches the parameter type, that instance is used. Otherwise, the most
recently created event of that type is used.
## Engineering constraints (anti-patterns)
### The dangling stop violation
**Constraint:** No step may depend on `StopEvent` or any subclass as an input parameter.
When a `StopEvent` is emitted, the run terminates. Subsequent steps are not scheduled.
```python
# VIOLATION: depending on stop event
@step()
async def cleanup(self, stop: LLMStopEvent) -> CleanupEvent:
... # Never executes
# CORRECT: use non-stop event, then explicit stop
@step()
async def respond(self, event: InputEvent, displayer: EventDisplayer) -> LLMEvent:
return await displayer.display_llm_stream(..., as_stop_step=False)
@step()
async def cleanup(self, llm: LLMEvent) -> CleanupEvent:
...
@step(precondition=cleanup_complete)
async def finalize(self, cleanup: CleanupEvent) -> StopEvent:
return StopEvent()
```
### The context smuggle violation
**Constraint:** Data must flow between steps via events, not `RunContext`.
Context-based data passing creates invisible dependencies, breaks the DAG, and causes race conditions.
```python
# VIOLATION: using context as data bus
@step()
async def step_a(self, event: InputEvent, run_context: RunContext) -> EventA:
await run_context.set("query", event.query) # Wrong
return EventA()
@step()
async def step_b(self, event: EventA, run_context: RunContext) -> OutputEvent:
query = await run_context.get("query") # Wrong — invisible dependency
...
# CORRECT: direct event dependency
@step()
async def step_b(self, event_a: EventA, original: InputEvent) -> OutputEvent:
query = original.query # Explicit, visible dependency
...
```
**Exception:** `RunContext` is valid for control flow state only (loop counters, recursion depth, retry tracking).
### The config lie violation
**Constraint:** Do not check `AgentConfig` inside a step to determine if you should have waited for an event.
By execution time, the dispatcher has already made the scheduling decision. The race condition has already occurred.
```python
# VIOLATION: config check inside step
@step()
async def process(self, required: EventA, optional: EventB | None = None, config: AgentConfig) -> OutputEvent:
if config.enable_feature and optional is None:
return None # Too late — step already executed with None
# CORRECT: precondition prevents premature scheduling
@step(precondition=check_ready)
async def process(self, required: EventA, optional: EventB | None = None, config: AgentConfig) -> OutputEvent:
# If config.enable_feature, precondition guarantees optional is not None
...
```
### The double-dip violation
**Constraint:** Do not reuse the same event type for multiple distinct logical stages.
The dispatcher queries by type. Downstream steps may trigger unpredictably or process the wrong instance.
```python
# VIOLATION: same event type for different stages
@step()
async def validate(self, event: InputEvent) -> ProcessedEvent:
return ProcessedEvent(data="validated")
@step()
async def enrich(self, event: ProcessedEvent) -> ProcessedEvent: # Same type!
return ProcessedEvent(data="enriched")
# CORRECT: distinct types for each logical state
@step()
async def validate(self, event: InputEvent) -> ValidatedEvent:
return ValidatedEvent(data="validated")
@step()
async def enrich(self, event: ValidatedEvent) -> EnrichedEvent:
return EnrichedEvent(data="enriched")
```
### The optional parameter trap
**Constraint:** Optional parameters without preconditions cause re-execution.
```python
# Executes twice: once with b=None, again with b=EventB
@step()
async def process(self, a: EventA, b: EventB | None = None) -> OutputEvent:
...
# Executes once when precondition satisfied
@step(precondition=check_b_ready)
async def process(self, a: EventA, b: EventB | None = None, config: AgentConfig) -> OutputEvent:
...
```
## Troubleshooting
### Step executes multiple times
**Symptom:** Phoenix/Langfuse trace shows duplicate step executions.
**Root cause:** Optional parameters without precondition. Step triggers when R(S) ⊆ E, then again when optional events
arrive.
**Diagnosis:**
1. Check step signature for `param: T | None = None`
2. Verify `@step(precondition=...)` is present
3. Verify precondition checks both config AND event presence
**Fix:** Add a precondition that returns `False` until all expected events arrive.
### Step never executes
**Symptom:** Step absent from trace.
**Root cause:** Precondition never returns `True`, or required event never emitted by an upstream step.
**Diagnosis:**
1. Add logging to precondition
2. Verify upstream steps emit expected events
3. Check config flags — a disabled feature may prevent event emission
**Fix:** Ensure precondition handles disabled features: `if not config.enable_feature: return True`
### Events after StopEvent
**Symptom:** Trace shows events timestamped after `StopEvent`.
**Root cause:** A step depends on `StopEvent` or a subclass (like `LLMStopEvent`), or an async step completes after
termination.
**Fix:** Use `LLMEvent` (not `LLMStopEvent`) with `as_stop_step=False`, then add an explicit final step with a
precondition.
### Precondition parameter mismatch
**Symptom:** Runtime error about unresolvable parameters.
**Root cause:** Precondition requests a type not injectable in the step's context.
**Fix:** Ensure precondition parameter types are a subset of the step's injectable types. Parameter names are
irrelevant; only types must match.
## Implementation checklist
### Before implementation
- [ ] Understand execution semantics (this page)
- [ ] Review the [memory lifecycle](../5_memory/) if using memory
- [ ] Study production agents in `packages/agent/swiss_ai_hub/agent/agents/rag_agent/` and `expert_rag_agent/`
### For each step
- [ ] Optional parameters have preconditions checking config AND event presence
- [ ] Precondition parameter types are a subset of step's injectable types
- [ ] Return type correctly indicates terminal (`StopEvent`) vs. non-terminal
- [ ] No dependency on `StopEvent` or its subclasses
### For memory integration
- [ ] LLM step uses `as_stop_step=False` (returns `LLMEvent`, not `LLMStopEvent`)
- [ ] Storage step depends on `LLMEvent`
- [ ] Final step has precondition waiting for storage completion
### After implementation
- [ ] Trace shows expected execution order (Langfuse or Phoenix)
- [ ] No duplicate step executions
- [ ] No events after `StopEvent`
- [ ] Tests cover all config flag combinations
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/10_events_reference.md
title: "Events Reference"
description: ""
---
# Events Reference
Events are the fundamental communication mechanism in the agent framework. This page provides a complete reference for
the event hierarchy, how to choose the right base event, and a catalog of all available events.
## Control vs display vs combined events
The framework distinguishes between event categories based on their effect on the workflow:
| Category | Base class | Triggers dispatcher | Use case |
| ----------------------- | ------------------------ | ------------------- | --------------------------------------------- |
| **Control** | `ControlEvent` | Yes | Workflow state transitions, step dependencies |
| **Display** | `DisplayEvent` | No | UI updates, streaming output, observability |
| **Control and Display** | `ControlAndDisplayEvent` | Yes | Both: advances workflow AND shows in UI |
**Control events** influence the workflow's control flow. When a `ControlEvent` is published, the dispatcher evaluates
all steps to determine if any new steps should execute. Only `ControlEvent` types (or subclasses) can satisfy step input
requirements.
**Display events** do not trigger the dispatcher. Use them for high-frequency updates that should appear in the UI but
should not cause step re-evaluation. A streamed LLM response may emit hundreds of `ChunkEvent` instances per minute;
making these control events would cause unnecessary dispatcher overhead.
**Combined events** need both behaviors — they influence workflow AND appear in the UI. Most semantic events
(`LLMEvent`, `RetrieverEvent`, etc.) inherit from `ControlAndDisplayEvent`.
**Protocol events are combined events.** Calls between steps and capabilities, such as a capability request and its
result or a tool-loop decision, are part of the protocol, and an admin reading a run needs to see them. The event
history lists display events only, so these inherit from `ControlAndDisplayEvent` and give themselves a `_display_name`
and `_display_description`. The description is a short progress phrase ("Recalling memories"), since chat clients such
as OpenWebUI show it as a live status while the agent works. Each one also has a component in the admin UI's event
history. Keep plain `ControlEvent` for internal bookkeeping no reader needs. Only the dispatcher's JetStream copy
triggers steps; the display copy goes out over NATS Core and never re-triggers anything.
```python
from swiss_ai_hub.core.events.control.control_event import ControlEvent
from swiss_ai_hub.core.events.display.display_event import DisplayEvent
from swiss_ai_hub.core.events.control_and_display_event import ControlAndDisplayEvent
# Control event: triggers dispatcher, can satisfy step dependencies
class AnalysisCompleteEvent(ControlEvent):
summary: str
# Display event: does NOT trigger dispatcher, UI-only
class ProgressUpdateEvent(DisplayEvent):
percent_complete: float
status_message: str
# Combined: triggers dispatcher AND displays in UI
class RetrieveEvent(ControlAndDisplayEvent):
nodes: list[IngestedNode]
```
## Events as flow carriers
Events serve two distinct purposes:
1. **Data carriers:** Transporting values between steps
2. **Flow carriers:** Controlling execution order independent of data
A step may depend on an event solely to ensure execution ordering, without requiring any data from it:
```python
class PathA(ControlEvent):
pass # No fields — pure flow control
class PathB(ControlEvent):
pass
@step()
async def decide(self, event: StartEvent) -> PathA | PathB:
if condition():
return PathA()
return PathB()
@step()
async def handle_path_a(self, _: PathA) -> StopEvent:
# Underscore signals: "I need this event for flow control, not data"
return StopEvent()
```
The `_: EventType` convention indicates dependency on an event's existence rather than its contents. This pattern is
essential for:
- **Conditional branching:** Different steps execute based on which event type was emitted
- **Sequencing:** Ensuring step B waits for step A without needing A's output data
- **Synchronization barriers:** Waiting for a signal that work completed
## Event hierarchy
```
BaseEvent
├── ControlEvent # Triggers dispatcher
│ ├── StartEvent # Workflow entry points
│ │ └── UserMessageEvent # User-initiated workflow
│ ├── StopEvent # Workflow termination
│ └── ExceptionEvent # Error signaling
│
├── DisplayEvent # UI-only, no dispatcher trigger
│ ├── ChunkEvent # Streaming text chunks
│ ├── ThoughtEvent # Agent reasoning display
│ └── CostEvent # Cost tracking display
│ └── LLMCostEvent # LLM-specific costs
│
├── ControlAndDisplayEvent # Both behaviors
│ ├── SemanticEvent # OpenInference-compatible (OTEL/Phoenix/Langfuse)
│ │ ├── LLMEvent # LLM invocation
│ │ │ └── LLMStopEvent # Terminal LLM response
│ │ ├── RetrieverEvent # Document retrieval
│ │ ├── RerankerEvent # Result reranking
│ │ ├── EmbeddingEvent # Embedding generation
│ │ ├── ToolEvent # Tool invocation
│ │ ├── GuardEvent # Guardrail evaluation
│ │ ├── ChainEvent # Chain execution
│ │ └── AgentEvent # Agent invocation
│ │
│ ├── HumanInTheLoopRequestEvent # HITL requests
│ ├── HumanInTheLoopResponseEvent # HITL responses
│ ├── AgentInTheLoopRequestEvent # AITL delegation
│ ├── AgentInTheLoopResponseEvent # AITL results
│ ├── BotInTheLoopRequestEvent # BITL Teams/Slack requests
│ ├── BotInTheLoopResponseEvent # BITL responses
│ │
│ ├── BaseRetrieveMemoryEvent # Memory retrieval
│ │ ├── RetrieveUserMemoryEvent
│ │ └── RetrieveOrganizationMemoryEvent
│ ├── BaseStoreMemoryEvent # Memory persistence
│ │ ├── StoreUserMemoryEvent
│ │ └── StoreOrganizationMemoryEvent
│ │
│ └── RouterEvent # LLM routing decisions
```
## Choosing the right base event
When creating a custom event, inherit from the most specific applicable base class:
| If your event represents... | Inherit from | Benefits |
| -------------------------------- | ------------------------------------------------------------- | ------------------------------------------- |
| Workflow start condition | `StartEvent` | Recognized as entry point |
| User message initiating workflow | `UserMessageEvent` | Chat history, locale, user identity |
| Workflow termination | `StopEvent` | Signals completion |
| Error/failure | `ExceptionEvent` | Error handling patterns |
| LLM invocation result | `LLMEvent` | Token counts, messages, OpenInference spans |
| LLM terminal response | `LLMStopEvent` | Combines LLM data with workflow termination |
| Document retrieval | `RetrieverEvent` | Retrieved nodes, OpenInference spans |
| Reranking operation | `RerankerEvent` | Input/output nodes, OpenInference spans |
| Embedding generation | `EmbeddingEvent` | Vectors, model info, OpenInference spans |
| Tool/function call | `ToolEvent` | Tool name, parameters, OpenInference spans |
| Guardrail check | `GuardEvent` | Guard result, OpenInference spans |
| Human approval needed | `HumanInTheLoopRequestEvent` | Workflow suspension, UI prompt |
| Agent delegation | `AgentInTheLoopRequestEvent` | Cross-agent communication |
| Memory retrieval | `RetrieveUserMemoryEvent` / `RetrieveOrganizationMemoryEvent` | Memory search results |
| Memory storage | `StoreUserMemoryEvent` / `StoreOrganizationMemoryEvent` | Memory persistence confirmation |
| Streaming text chunk | `ChunkEvent` | Real-time UI updates (display-only) |
| Agent thought/reasoning | `ThoughtEvent` | Transparency display (display-only) |
| Cost information | `LLMCostEvent` | Token usage, pricing (display-only) |
| Generic workflow state | `ControlAndDisplayEvent` | Triggers dispatcher + UI display |
| Generic UI update | `DisplayEvent` | UI-only, no dispatcher overhead |
## Semantic events and OpenInference
`SemanticEvent` subclasses implement the `to_semantic_convention()` method, producing attributes compatible with the
[OpenInference specification](https://github.com/Arize-ai/openinference). This enables integration with:
- **Arize Phoenix:** Trace visualization and debugging
- **Langfuse:** LLM observability and analytics
- **Any OpenTelemetry-compatible system:** Standard span attributes
```python
from swiss_ai_hub.core.events.semantic import RetrieverEvent
# RetrieverEvent automatically exports OpenInference attributes:
# - openinference.span.kind: "RETRIEVER"
# - retrieval.documents.{i}.document.id
# - retrieval.documents.{i}.document.content
# - retrieval.documents.{i}.document.score
event = RetrieverEvent.from_nodes(retrieved_nodes)
otel_attributes = event.to_semantic_convention()
```
When building agents that require observability, prefer semantic events over generic `ControlAndDisplayEvent`:
```python
# Preferred: semantic event for observability
from swiss_ai_hub.core.events.semantic import RetrieverEvent
@step()
async def retrieve(self, event: UserMessageEvent) -> RetrieverEvent:
nodes = await retriever.retrieve(event.user_query)
return RetrieverEvent.from_nodes(nodes) # OpenInference-compatible
# Discouraged: generic event loses observability benefits
class MyRetrieveEvent(ControlAndDisplayEvent):
nodes: list[NodeWithScore] # No OpenInference integration
```
## Available events reference
### Control events (trigger dispatcher)
| Event | Module | Purpose |
| ---------------- | ---------------------------------- | --------------------------------- |
| `ControlEvent` | `control.ControlEvent` | Base class for all control events |
| `StartEvent` | `control.start.StartEvent` | Workflow entry point |
| `StopEvent` | `control.stop.StopEvent` | Workflow termination |
| `ExceptionEvent` | `control.exception.ExceptionEvent` | Error signaling |
### Display events (UI only)
| Event | Module | Purpose |
| -------------- | ---------------------- | ----------------------------- |
| `DisplayEvent` | `display.DisplayEvent` | Base class for display events |
| `ChunkEvent` | `display.ChunkEvent` | Streaming text output |
| `ThoughtEvent` | `display.ThoughtEvent` | Agent reasoning transparency |
| `CostEvent` | `cost.CostEvent` | Cost tracking base |
| `LLMCostEvent` | `cost.LLMCostEvent` | LLM token/cost reporting |
### Semantic events (OpenInference compatible)
| Event | Module | OpenInference span kind |
| ---------------- | ----------------------------------- | ----------------------- |
| `SemanticEvent` | `semantic.SemanticEvent` | Base class (abstract) |
| `LLMEvent` | `semantic.llm.LLMEvent` | `LLM` |
| `LLMStopEvent` | `semantic.llm.LLMStopEvent` | `LLM` (terminal) |
| `RetrieverEvent` | `semantic.retriever.RetrieverEvent` | `RETRIEVER` |
| `RerankerEvent` | `semantic.reranker.RerankerEvent` | `RERANKER` |
| `EmbeddingEvent` | `semantic.embedding.EmbeddingEvent` | `EMBEDDING` |
| `ToolEvent` | `semantic.tool.ToolEvent` | `TOOL` |
| `GuardEvent` | `semantic.guard.GuardEvent` | `GUARDRAIL` |
| `ChainEvent` | `semantic.chain.ChainEvent` | `CHAIN` |
| `AgentEvent` | `semantic.agent.AgentEvent` | `AGENT` |
### Interaction events
| Event | Module | Purpose |
| ---------------------------------------- | ----------------------------- | ----------------------------------- |
| `UserMessageEvent` | `user.UserMessageEvent` | User-initiated workflow start |
| `HumanInTheLoopRequestEvent` | `human_in_the_loop.request` | Base class for HITL requests |
| `HumanInTheLoopInputRequestEvent` | `human_in_the_loop.request` | Popup with text input field |
| `HumanInTheLoopConfirmationRequestEvent` | `human_in_the_loop.request` | Yes/No button selection |
| `HumanInTheLoopChatRequestEvent` | `human_in_the_loop.request` | Chat message (fallback) |
| `HumanInTheLoopResponseEvent` | `human_in_the_loop.response` | Base class for HITL responses |
| `AgentInTheLoopRequestEvent` | `agent_in_the_loop.request` | Delegate to another agent |
| `AgentInTheLoopResponseEvent` | `agent_in_the_loop.response` | Delegated agent result |
| `AgentInTheLoopExceptionEvent` | `agent_in_the_loop.exception` | Delegated agent failure |
| `BotInTheLoopRequestEvent` | `bot_in_the_loop.request` | Send message to Teams/Slack channel |
| `BotInTheLoopResponseEvent` | `bot_in_the_loop.response` | Response from Teams/Slack user |
**HITL helper classes** (not events, but workflow utilities):
| Helper | UI behavior |
| ---------------------------- | ----------------------------------------------------- |
| `HumanInTheLoopInput` | Popup dialog for free-form text entry |
| `HumanInTheLoopConfirmation` | Yes/No button selection |
| `HumanInTheLoopChat` | Message in chat stream (fallback for simple UIs/APIs) |
### Memory events
| Event | Module | Purpose |
| --------------------------------- | ----------------- | ---------------------------- |
| `BaseRetrieveMemoryEvent` | `memory.retrieve` | Memory retrieval base |
| `RetrieveUserMemoryEvent` | `memory.retrieve` | User-scoped memory retrieval |
| `RetrieveOrganizationMemoryEvent` | `memory.retrieve` | Org-scoped memory retrieval |
| `BaseStoreMemoryEvent` | `memory.store` | Memory storage base |
| `StoreUserMemoryEvent` | `memory.store` | User-scoped memory storage |
| `StoreOrganizationMemoryEvent` | `memory.store` | Org-scoped memory storage |
| `AddMemoryToChatHistoryEvent` | `memory.history` | Extended chat history |
### Guard events
| Event | Module | Purpose |
| -------------------------------- | --------------------------- | ---------------------------- |
| `GuardAcceptEvent` | `guard.GuardAcceptEvent` | Guard passed |
| `GuardRejectionEvent` | `guard.GuardRejectionEvent` | Guard rejected |
| `AgentSuitabilityAcceptEvent` | `guard` | Agent can handle request |
| `AgentSuitabilityRejectEvent` | `guard` | Agent cannot handle request |
| `ContextSufficientAcceptEvent` | `guard` | Sufficient context available |
| `ContextInsufficientRejectEvent` | `guard` | Insufficient context |
| `SensitiveInfoAcceptEvent` | `guard` | No sensitive info detected |
| `SensitiveInfoRejectEvent` | `guard` | Sensitive info detected |
### Capability events
Requests and results of the capabilities every chat blueprint calls (see the agent SDK's capabilities). All of them are
`ControlAndDisplayEvent`s, unless noted otherwise.
| Event | Module | Purpose |
| --------------------------------- | -------------------------------- | --------------------------------------------------------------------- |
| `ContextualizeConversationEvent` | `conversation` | Asks for the turn's query, meta-question gate and title |
| `ConversationContextualizedEvent` | `conversation` | The limited history and the query the turn is answered for |
| `ComposeContextEvent` | `conversation` | Asks for the prompt with context blocks merged in |
| `ContextComposedEvent` | `conversation` | The history the model receives, within the input budget |
| `CompleteConversationEvent` | `conversation` | Ends the turn: follow-up questions, then the stop event |
| `NotAMetaQuestionEvent` | `self_awareness` | The message is a normal request; releases the entry steps |
| `RecallMemoryEvent` | `memory.recall` | Asks for the user and organization memories relevant to a query |
| `MemoryRecalledEvent` | `memory.recall` | One block per memory scope |
| `MemoryStorageRequestedEvent` | `memory.request` | Starts the memory writer's own run, without waiting for it |
| `SearchKnowledgeEvent` | `knowledge` | Asks for the referenced collections' best sections for a query |
| `KnowledgeSearchedEvent` | `knowledge` | The block found and its sections, listed as sources |
| `ReadAttachedFilesEvent` | `attached_file` | Asks for the message's files, fitted to the room left |
| `AttachedFilesReadEvent` | `attached_file` | The files' contents as one context block |
| `AttachedFileEvent` | `attached_file` (`DisplayEvent`) | One file: the text the model received, its status and its citation id |
### Tool-loop events
The model deciding which tools to use, until it is done. All of them are `ControlAndDisplayEvent`s unless noted. The
loop's state travels on these events, so the history shows every iteration.
| Event | Module | Purpose |
| --------------------------- | ---------------------------- | --------------------------------------------------------------- |
| `RunToolLoopEvent` | `tool_loop` | Asks the loop to run a tool set, answering or gathering context |
| `ToolLoopIterationEvent` | `tool_loop` | The model's turn to decide, one per iteration |
| `ToolCallsDecidedEvent` | `tool_loop` | The tools the model chose this iteration |
| `ToolCallApprovedEvent` | `tool_loop` | One call cleared to run, after any approval |
| `ToolResultEvent` | `tool_loop` | One call's result, shown in the tool's block in the chat |
| `ToolLoopFinishedEvent` | `tool_loop` | The reply (answering) or the gathered context (gathering) |
| `ToolLoopStatusEvent` | `tool_loop` (`DisplayEvent`) | What the loop is doing while it gathers in the background |
| `ToolLoopCondensedEvent` | `tool_loop` (`DisplayEvent`) | Earlier results or turns condensed to fit the model's context |
| `ToolApprovalRequestEvent` | `tool_loop` | A yes/no confirmation before a tool runs (a HITL request) |
| `ToolApprovalResponseEvent` | `tool_loop` | The user's answer to it |
### Utility events
| Event | Module | Purpose |
| ---------------------------------- | ------------------------------ | ---------------------- |
| `RouterEvent` | `router.RouterEvent` | LLM routing decision |
| `LanguageEvent` | `common.LanguageEvent` | Language detection |
| `LimitChatHistoryEvent` | `common.LimitChatHistoryEvent` | Truncated history |
| `StandaloneQuestionCondenserEvent` | `common` | Question reformulation |
## Custom events
Custom events are Pydantic models. All fields require type annotations:
```python
from swiss_ai_hub.core.events.control.control_event import ControlEvent
class AnalysisCompleteEvent(ControlEvent):
summary: str
confidence: float
findings: list[str]
metadata: dict | None = None
```
::: warning The stop event constraint
No step may depend on `StopEvent` or any subclass as an input parameter. When a `StopEvent` is emitted, the run
terminates and subsequent steps are not scheduled. See the
[execution model](../9_execution_model/#the-dangling-stop-violation) for details and the correct pattern.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/11_using_mcp_tools.md
title: "Using MCP Tools"
description: "Connect agents to external MCP servers and let them call the tools those servers expose."
---
# Using MCP Tools
Agents are not limited to the steps you write and the knowledge you index — they can also call tools hosted on
**external MCP servers**. The [Model Context Protocol](https://modelcontextprotocol.io) is an open standard for exposing
tools and data to AI applications. An agent connects to any MCP server and lets its LLM call the tools it offers: create
a ticket, query a database, send a message.
## The connection config
`McpClientConfig` describes a single MCP server connection. It is a `StepConfig`, so the dispatcher injects it into any
step that declares it as a parameter — add it to your agent config and it becomes available in your workflow.
| Field | Purpose |
| ----------- | -------------------------------------------------------- |
| `name` | A logical name for the connection. |
| `url` | The MCP server endpoint — FastMCP infers the transport. |
| `auth_mode` | How to authenticate: `none`, `api_key`, or `user_token`. |
| `api_key` | The static key, used only when `auth_mode` is `api_key`. |
| `timeout` | Client timeout in seconds. |
## Authentication modes
| Mode | Behavior |
| ------------ | -------------------------------------------------------------------------------------------- |
| `none` | No credentials are sent. |
| `api_key` | A static key configured on the connection is sent as the bearer token. |
| `user_token` | The requesting user's token is forwarded, so the external action is attributed to that user. |
`user_token` mode only works for user-initiated chat runs that carry the user's token. Scheduled, process-initiated,
bot-channel, and agent-to-agent runs forward no user token and fail in this mode — use `api_key` or `none` for those.
## Calling MCP tools from a step
Add an `McpClientConfig` field to your agent config, then open a client inside a step with `McpClientFactory`.
`McpAuthResolver` reads the forwarded user token out of the run context, so a connection set to `user_token` mode acts
on behalf of the requesting user.
```python
from swiss_ai_hub.core.mcp import McpClientConfig
from swiss_ai_hub.agent.mcp import McpAuthResolver, McpClientFactory
class ResearchAgentConfig(AgentConfig):
mcp: McpClientConfig
class ResearchAgent(Agent):
@step()
async def call_tools(
self,
event: UserMessageEvent,
mcp_config: McpClientConfig,
run_context: RunContext,
) -> StopEvent:
user_token = await McpAuthResolver.resolve_user_token(run_context)
async with McpClientFactory.create(mcp_config, user_token=user_token) as mcp_client:
tools = await mcp_client.list_tools()
result = await mcp_client.call_tool("create_ticket", {"title": "..."})
...
```
::: warning
`McpClientFactory` raises if `auth_mode` is `api_key` or `user_token` but the credential is missing. A misconfigured
connection fails loudly instead of silently calling the server unauthenticated or with the wrong identity.
:::
## The ready-made McpReactAgent
If you just need an agent that reasons over an MCP server's tools, the SDK ships **`McpReactAgent`** — a complete
ReAct-style agent that discovers an MCP server's tools and resources, lets the LLM pick and call them in a loop, and
streams the result. Point its `McpClientConfig` at your server, choose an `auth_mode`, and run it. It is also the
reference implementation for the pattern above.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/2_building_agents/12_capabilities_and_tools.md
title: "Capabilities and Tools"
description: "Reuse the steps every chat agent needs through capabilities, and let the model choose tools with the tool loop."
---
# Capabilities and Tools
Every conversational agent repeats the same work: derive a query from the history, recall memory, read attached files,
search referenced knowledge, put it all in front of the model, and finish with a title and follow-up questions. The SDK
packages this as **capabilities** that a blueprint calls with events. Because a capability is called, not installed,
reading a blueprint shows which context reaches the model and what runs after the answer.
## Calling a capability
A capability is called the way human-in-the-loop requests are: a step returns the capability's **request event**, built
with a typed helper, and a later step declares the **result event** as a parameter. Returning a list fans out, and
declaring several parameters waits for all of them.
| Capability | Call | Result | Config mixin |
| --------------- | --------------------------------------------------------- | ----------------------------- | --------------------- |
| `Conversation` | `contextualize(history, message)` | `Conversation.Contextualized` | `ConversationFields` |
| `Conversation` | `compose(history, blocks)` | `Conversation.Composed` | `ConversationFields` |
| `Conversation` | `complete(answer, stop=None)` | ends the run | `ConversationFields` |
| `Memory` | `recall(query)`; `remember(...)` stores in the background | `Memory.Recalled` | `MemoryFields` |
| `AttachedFiles` | `read(files, history, query)` | `AttachedFiles.Contents` | `AttachedFilesFields` |
| `Knowledge` | `search(references, query)` | `Knowledge.Searched` | `KnowledgeFields` |
The blueprint's config lists the mixins of the capabilities it calls before `AgentConfig`, and the dispatcher injects
the run's config into any step parameter annotated with one. The chat blueprints in
`packages/agent/swiss_ai_hub/agent/agents/llm_wrapping_agent/` are the smallest complete example: one step limits and
contextualizes, one gathers memory, files and referenced knowledge in parallel, one composes, one answers and completes.
What the calls do for you:
- **`contextualize`** runs the meta-question check, derives the query the turn is answered for and titles the thread.
- **`compose`** puts the gathered blocks behind the leading system messages, within the input budget, and hands the
model **one** system message. Older turns give way before the blocks do. The event history shows the result as what
the model saw.
- **`complete`** generates follow-up questions and ends the run with a stop event. A stop event passed to it travels
inside the request, so declare it in `Agent.completion_stops`, or the REST response model and workflow graph show a
bare `StopEvent`.
- **Citations.** Documents a capability hands to the model carry a short stable id the model cites as `[s3f9a1c]`; the
Open WebUI integration turns the ids into numbered source chips.
### Validation
A workflow is validated when the runner starts. It refuses a step that waits for an event nobody produces, a call whose
result no step consumes, duplicate step names, and a config missing a required mixin, and the message names the
obligation. A capability that is called but has nothing to do (memory switched off, no files attached) still answers
with an empty result, so waits never hang.
### Who is asking
A step that declares `AccessChecker | None` receives the asking user's access rules, or `None` for a run without a user
(scheduled or mail-triggered). Use it to narrow retrieval to what the user may read; the knowledge capability and the
RAG blueprints do.
### Chat features
Web Search, Code Interpreter and Image Generation toggles in Open WebUI arrive as `UserMessageEvent.requested_features`
(the OpenAI-compatible API accepts the same list in `metadata.features`). A capability declares the feature it serves,
and an agent only shows, and is only sent, features its capabilities support. `RequestedFeatures.contains(...)` lets a
step or precondition check one.
## The tool loop
When the path to an answer cannot be written as steps, the `ToolLoop` capability lets the model decide which tools to
use, runs them, and lets it decide again until it can answer. A blueprint declares tool sets as class attributes and
starts a loop wherever it fits.
```python
class AnsweringToolLoopAgent(Agent):
tools = ToolLoop.over(Knowledge, ClockTools)
@step()
async def loop_step(self, ctx: Conversation.Contextualized) -> ToolLoop.RunRequest:
return AnsweringToolLoopAgent.tools.run(ctx.history)
@step()
async def complete_step(self, finished: ToolLoop.Finished) -> Conversation.CompleteRequest:
return Conversation.complete(answer=finished.answer)
```
`tools.run(history)` ends with the model's reply (`ANSWER` mode). `tools.route(history)` makes one gathering decision
and returns the results as a context block for the blueprint's own answer. Every event of a loop carries the set's name,
so a blueprint can run several sets in a row.
### Three kinds of tool
All look the same to the model.
- **Capability tools.** A capability that offers itself as a tool, such as `Knowledge` (which searches the collections
in the profile's `knowledge_tool` settings, or every collection the user can read, plus those referenced on the
message, limited to what the user may read). A model-chosen call produces the same events and citation chips as an
explicit one. The config must also list the capability's tool mixin, `KnowledgeToolFields` for `Knowledge`.
- **LlamaIndex tool specs.** A `BaseToolSpec` subclass whose listed methods are the tools; the schema is read from
signatures and docstrings, so LlamaHub specs work unchanged. It is built per run with a `ToolContext` (config, user,
locale, displayer).
- **LlamaIndex tools.** Any `BaseTool`, for example `FunctionTool.from_defaults(...)`.
A tool that raises returns an error result to the model instead of ending the run. `@ToolOptions.of(...)` adds what
users need and the model does not: a localized label, an approval summary, an approval default, and the chat toggle the
tool needs.
### What stays under the admin's control
The runner publishes the loop's form with the blueprint's tools as options, so profiles get limits (decisions and tool
calls), a list of disabled tools, and per-tool approvals (never, every call, once per answer, once per conversation).
Approval is asked as a yes/no confirmation in the chat; a declined tool is withdrawn for the rest of the answer. Only
tools actually offered can run: a call to a disabled tool, a tool whose toggle is off, or a name the model made up goes
back to it as an unknown tool. At a limit the model answers without tools and a notice says it stopped early. When the
conversation and the tool schemas outgrow the input budget, earlier results and turns are condensed in place and the
chat shows a status.
### Events
Each stage is an event, so the loop is as inspectable as any workflow: `ToolCallsDecidedEvent` with a `ToolEvent` per
call, `ToolApprovalRequestEvent` and `ToolApprovalResponseEvent`, `ToolResultEvent`, `ToolLoopIterationEvent` per round
and `ToolLoopStatusEvent` while the model decides. The playground at
`packages/agent/playground/minimal_workflow/tool_loop_workflow/` shows an answering and a gathering agent side by side.
## Free-form map fields in events
Start, stop and human-in-the-loop events are rebuilt from the JSON Schema an agent announces, so the REST endpoints
validate them. Map fields such as `dict[str, bool]` are preserved, including nested ones; use them freely in event
payloads.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/3_building_pipelines.md
title: "Building Pipelines"
description: ""
---
# Building Pipelines with the Swiss AI Hub SDK
The Swiss AI Hub Pipeline SDK provides a powerful, production-ready framework for building document processing
pipelines. It's designed to ingest documents from various sources, parse them, and create searchable vector embeddings
for Retrieval-Augmented Generation (RAG) systems.
This guide explains the SDK's architecture and shows you how to configure and deploy robust, automated data pipelines.
## The Default Data Lake to Vector Store Pipeline
The SDK's core is a pre-built, configurable pipeline that handles the entire journey from raw files in a data lake to
indexed embeddings in a vector store.
```mermaid
graph TD
subgraph "Source Systems"
A["📁 SharePoint Sites"]
B["📂 File Systems"]
C["📄 Wikis"]
D["📤 Manual Uploads"]
E["🔗 Other Sources"]
end
subgraph "Default Ingestion Pipeline"
F("Data Lake")
G["Document Parsing"]
H("Document Store")
I["Chunking & Embedding"]
J("Vector Store")
end
subgraph "Consumption"
K["🤖 RAG Agents"]
end
A -->|Raw Files| F
B -->|Raw Files| F
C -->|Wiki Content| F
D -->|Uploaded Files| F
E -->|External Data| F
F -->|DataLakeFile| G
G -->|RefDocDocument| H
H -->|RefDocDocument| I
I -->|TextNodes | J
J -->|TextNodes | K
style F fill:#3a5ccc,stroke:#3451b2,color:#ffffff
style H fill:#299764,stroke:#18794e,color:#ffffff
style J fill:#7e4cc9,stroke:#6f42c1,color:#ffffff
```
## Key Principles
Our SDK is built on a few key principles to ensure pipelines are efficient, scalable, and maintainable:
- **Asset Factories**: Instead of writing boilerplate, you use simple factory functions to generate entire sets of
pre-configured assets and resources (e.g., `document_ingestion_pipeline_definitions`).
- **Change-Driven Automation**: Pipelines run automatically in response to data changes, not on fixed schedules. This is
achieved using **observable assets** that monitor source systems.
- **Document-Level Isolation**: Each document is processed in its own **partition**, meaning a failure in one document
won't halt the entire pipeline.
- **Pluggable I/O**: Custom **I/O Managers** abstract away storage logic, making it easy to integrate with different
databases like MongoDB and Milvus without changing your core processing code.
## Quick Start: A Complete Pipeline in Under 10 Lines
The SDK's factories make it incredibly simple to stand up a complete pipeline. The
`document_ingestion_pipeline_definitions` function bundles all the necessary assets, resources, jobs, and schedules.
Create a file named `my_pipeline.py`:
```python
from swiss_ai_hub.core.i18n import LocaleString
from swiss_ai_hub.core.infrastructure import DocumentIngestionPipelineSettings
from swiss_ai_hub.pipeline.util import document_ingestion_pipeline_definitions
# This single function call creates a complete, production-ready pipeline that serves every
# knowledge database assigned to this ingestor, resolving the target per run.
defs = document_ingestion_pipeline_definitions(
ingestor="my_rag",
display_name=LocaleString(en="My RAG"),
description=LocaleString(en="Tuned for my documents"),
# The models and enrichment switches this deployment defaults to, from DOCUMENT_INGESTION_*;
# each database picks its own in the UI.
settings=DocumentIngestionPipelineSettings()
)
```
To run it, simply point the Dagster UI to your file: `dagster dev -f my_pipeline.py`
This single function call provides:
- An **observable data lake** per knowledge database, detecting new or changed documents.
- A multi-stage processing workflow including **parsing**, **chunking**, and **embedding**.
- Integration with MongoDB for a **document store** and Milvus for a **vector store**.
- Pre-configured **jobs**, **schedules**, and **sensors** for production-ready automation.
- Registration as a selectable ingestor, so users can create databases for it from the admin UI with no redeploy,
together with the **configuration form** its databases are created through: model pickers, enrichment switches, and
any setting you add yourself, pre-filled with the defaults above.
## Making your pipeline selectable in the UI
Passing `display_name` and `description` alongside your `ingestor` is what makes your pipeline appear in the
create-database dialog. There is nothing else to install and nothing to change in the platform.
Your pipeline advertises itself. A sensor it ships with publishes the labels, the configuration form and the form's
schema to the platform database on a short interval, and the API reads them from there. This is the same contract an
agent blueprint has with the platform: the dialog renders whatever the form declares, and the API validates every
submission against the schema. A setting you declare on your config class is therefore offered, checked and stored
without touching the API or the UI. Registration goes through the database rather than an in-process registry because
the API and the pipelines run in **separate containers**. Whether an ingestor exists is decided by what is deployed, not
by what happens to be importable inside the API image. Using a sensor rather than a module-level write means a momentary
database outage cannot stop your code location from loading; it simply registers on the next tick.
Three rules are worth knowing before you choose an id:
- **The labels are required.** A custom ingestor without `display_name` and `description` raises at definition time,
because unlabelled it could only ever render as a bare id in the selector.
- **Some ids are reserved.** The platform's own routing tokens — including the frozen legacy ones, which stay reserved
so nothing can adopt a legacy corpus — and `datalake`, which would collide in the event-subject grammar.
- **The id is permanent.** Asset keys, the partition registry, job names, the event stream and the storage prefix are
all derived from it, so changing it later strands every database already assigned to the old value. Choose it once.
If your pipeline never appears in the dialog, check in order: the code location loaded, its registration sensor is
running in the Dagster UI, the row exists in the platform database, and the API can reach that same database.
To add a setting of your own, extend `DocumentIngestionConfig` with a field such as `crawl_depth: int | InputNumber`,
give that field an element in your `as_form()` override, pass the result to the factory as `config=`, and read it back
per run in your ops. The class works the same way an agent's config does, so a nested section or a repeated entry needs
no extra work.
Setting the field in `as_form()` is what announces it. A field declared on the class but left at a plain default is not
part of the form, is not part of the schema, and a configuration that carries it is rejected with a 400 naming it —
rather than being stored and silently ignored at ingestion time. Full details, including the exact call, are in the
[package README](https://github.com/bbvch-ai/aihub-core/tree/main/packages/pipeline#making-a-custom-pipeline-selectable-in-the-ui).
## Next Steps
1. **[Pipeline Fundamentals](./1_pipeline_fundamentals/)** - Understand the architectural decisions and patterns for
building pipelines
2. **[Core Patterns](./2_core_patterns/)** - Understand the core patterns for building pipelines with examples
3. **[Data Ingestion Pipeline](./3_data_ingestion_pipeline/)** - Configure and extend the default pipeline
4. **[Job Scheduling](./4_job_scheduling/)** - Schedule your pipelines for automatic runs
5. **[Pipeline Observation](./5_pipeline_observation/)** Monitor your pipelines for performance and errors
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/3_building_pipelines/1_pipeline_fundamentals.md
title: "Pipeline Fundamentals"
description: ""
---
# Pipeline Fundamentals
The Swiss AI Hub Pipeline SDK is built on [Dagster](https://docs.dagster.io/). This page introduces the essential
building blocks that form every pipeline.
## Assets: The Data
An **asset** is the central concept in a pipeline. It represents a specific piece of data, such as a set of parsed
documents or a collection of vector embeddings. The code you write for an asset is the function that produces this data.
- **`@asset` / `@graph_asset`**: These decorators define a function as an asset. A `graph_asset` is a special type
composed of multiple smaller operations (`@op`) wired together.
- **Role**: Assets define the "what" of your pipeline - the transformations that turn raw files into valuable, AI-ready
data.
## I/O Managers
An **I/O Manager** handles the physical storage and retrieval of assets. They are the "plumbing" that connects one asset
to the next.
- **What they do**: When an asset produces data (e.g., a `RefDocDocument`), its I/O manager is responsible for saving it
to a specific storage system (like MongoDB). When a downstream asset needs that data, the I/O manager knows how to
load it.
- **Role**: I/O Managers abstract away storage logic. Your asset code doesn't need to know *where* or *how* data is
stored, making your pipeline highly modular and easy to reconfigure.
## Resources: The External Connections
A **Resource** manages the connection to an external system, such as a database, an API, or a file store.
- **What they do**: Resources handle the configuration, authentication, and connection clients needed to interact with
the outside world. For example, the `MongoDocumentStoreResource` manages the connection string and client for talking
to MongoDB.
- **Role**: Resources separate your pipeline's logic from its environment configuration, allowing the same pipeline code
to run seamlessly across development, testing, and production.
______________________________________________________________________
## Core Architectural Principles
- Instead of running entire pipelines on a fixed schedule, the SDK uses **observable source assets**. These assets
monitor a data source (like an S3 bucket) and only trigger downstream processing when a file is added or changed. This
is highly efficient, saving time and compute resources.
- Each document is processed in its own **partition**. This means a failure in one document won't halt the entire
pipeline. The other documents will continue processing independently. This also allows for massive parallelism, as
Dagster can process many partitions at once.
- To reduce boilerplate and enforce consistency, the SDK relies heavily on the **factory pattern**. Instead of writing
complex asset and resource definitions from scratch, you use simple factory functions (e.g., `documents_factory`,
`document_ingestion_pipeline_definitions`) that generate fully configured pipeline components for you.
## Next Steps
Now that you understand the fundamental components, explore the **[Core Pipeline Patterns](../2_core_patterns/)** to see
how these concepts are implemented in code to build powerful workflows.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/3_building_pipelines/2_core_patterns.md
title: "Core Patterns"
description: ""
---
# Core Pipeline Patterns
This page provides practical code examples for the concepts introduced in
[Pipeline Fundamentals](../1_pipeline_fundamentals/). These are the patterns you will use to build, customize, and
extend document processing pipelines with the `swiss_ai_hub.pipeline` SDK.
## Change Detection with Observable Assets
This pattern is the trigger for our automated pipelines. An `observable_source_asset` monitors an external data source
and yields a new data version for each file, typically by combining its timestamp and content hash. Dagster only
materializes downstream assets if this version has changed.
```python
# From observable_data_lake_factory.py
@observable_source_asset(
key=AssetKey(["data_lake"]),
partitions_def=document_partitions,
description="Observes the data lake for new or changed files",
)
def observable_data_lake(context: OpExecutionContext) -> DataVersionsByPartition:
# 1. Fetch all files from the data lake source
data_lake_files: list[DataLakeFile] = fetch_all_files_in_data_lake_no_op(...)
# 2. For each file's partition, create a unique version key
# This tells Dagster whether the file is new or has been modified.
return DataVersionsByPartition({
file.uri: f"{file.updated}-{file.hash}" for file in data_lake_files
})
```
## Per-Document Processing with Dynamic Partitions
Each document discovered by the observable asset is assigned its own partition, allowing for isolated and parallel
processing.
- **`DynamicPartitionsDefinition`**: Defines a set of partitions that can grow over time.
- **`automation_condition=AutomationCondition.eager()`**: This tells Dagster to automatically run this asset for a
partition as soon as its upstream dependency (the observable asset) has a new version for that partition.
```python
# Define a set of partitions that will be populated with document URIs
document_partitions = DynamicPartitionsDefinition(name="my_documents")
@graph_asset(
key=AssetKey(["documents"]),
ins={"data_lake_file": AssetIn(key=AssetKey(["data_lake"]))},
partitions_def=document_partitions,
automation_condition=AutomationCondition.eager(),
)
def documents(data_lake_file: DataLakeFile) -> RefDocDocument:
"""This asset will execute once for each new or changed document."""
return process_single_document(data_lake_file)
```
## Abstracting Storage with I/O Managers
I/O Managers connect assets by handling data persistence. They are configured as resources and assigned to assets.
```python
# From DocStoreIOManager.py
class DocStoreIOManager(ConfigurableIOManager):
doc_store: ResourceDependency[KVDocumentStore]
def handle_output(self, context: OutputContext, obj: RefDocDocument) -> None:
"""Saves the RefDocDocument produced by an asset to MongoDB."""
context.log.info(f"Adding document to docstore: {obj.id_}")
self.doc_store.add_documents([obj])
def load_input(self, context: InputContext) -> RefDocDocument:
"""Loads a RefDocDocument from MongoDB for a downstream asset."""
doc_id = self._convert_partition_key_to_doc_id(context.partition_key, context)
document = self.doc_store.get_document(doc_id)
return RefDocDocument(**document.to_dict())
# In your definitions, you provide the I/O Manager as a resource
defs = Definitions(
assets=[...],
resources={
"doc_store": MongoDocumentStoreResource(...),
"doc_store_io_manager": DocStoreIOManager(),
}
)
```
## Composing Logic with Graph Assets
A `graph_asset` is an asset composed of multiple smaller functions called **ops** (`@op`). This allows you to build
complex transformations while keeping each piece of logic simple and reusable.
```python
# From documents_factory.py
@graph_asset(key=key, ...)
def document(data_lake_file: DataLakeFile) -> Output[RefDocDocument]:
"""
This graph asset defines a multi-step process for a single document.
The output of one op flows directly into the next.
"""
# Op 1: Parse the raw file
parsed_doc = parse_document_from_data_lake(data_lake_file)
# Op 2: Add default metadata
doc_with_metadata = ensure_refdoc_default_metadata(parsed_doc)
# Op 3: Save to the document store (via I/O Manager)
return insert_ref_doc_into_docstore(doc_with_metadata)
```
## Building Reusable Pipelines with Factories
Factories are the highest level of abstraction in the SDK. They are functions that generate fully configured assets and
resources, allowing you to define an entire pipeline in just a few lines of code.
- **Asset Factories (`*_factory.py`)**: Functions that create individual, configured assets (like `documents_factory`).
- **Resource Factories (`resources/factory.py`)**: Functions that assemble a complete set of resources needed for a
pipeline (like `s3_data_lake_resources`).
- **Definitions Factories (`document_ingestion_definitions_util.py`)**: The top-level factory
(`document_ingestion_pipeline_definitions`) that uses all other factories to create a complete, runnable `Definitions`
object.
```python
# From document_ingestion_definitions_util.py
def document_ingestion_pipeline_definitions(ingestor: str = "document_ingestion", ...) -> Definitions:
"""
A factory that assembles an entire pipeline from other factories.
"""
# Use asset factories to create the assets
observable_asset = observable_data_lake_factory(...)
documents_asset = documents_factory(...)
nodes_asset = nodes_factory(...)
assets = [observable_asset, documents_asset, nodes_asset]
# Use resource factories to create the resources
resources = {
**default_io_manager_s3_datalake_resources(container_name=datalake_container_name),
**local_mongo_milvus_storage_context_resource(...),
# ... other resources
}
return Definitions(assets=assets, resources=resources, ...)
```
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/3_building_pipelines/3_data_ingestion_pipeline.md
title: "Data Ingestion Pipeline"
description: ""
---
# Data Ingestion Pipeline
The Swiss AI Hub Pipeline SDK provides pre-built, production-ready pipeline definitions that you can use with minimal
configuration. These **factories** encapsulate best practices for ingesting documents and preparing them for RAG
applications.
## The Two-Stage Ingestion Architecture
Our ingestion process is split into two distinct stages, each handled by its own pipeline definition factory. This
promotes modularity and reusability.
1. **Stage 1: Source to Data Lake** (Optional): This pipeline connects to an external source (like SharePoint) and syncs
its files to a central S3 data lake. It is configured per knowledge database from the UI.
2. **Stage 2: Data Lake to Vector Store**: This pipeline monitors the S3 data lake, processes the documents, and stores
the resulting embeddings in a vector store.
```mermaid
graph TD
subgraph "Source Systems"
A[SharePoint Sites]
B[Confluence Wiki]
C[Jira Projects]
D[Manual Uploads]
E[File Systems]
F[Other Sources]
end
subgraph "Pipeline"
G(Data Lake)
H[Document Parsing]
I(Document Store)
J[Vector Embedding]
K(Vector Store)
end
subgraph "Consumption"
L(RAG Agents)
end
A --> G
B --> G
C --> G
D --> G
E --> G
F --> G
G --> H
H --> I
I --> J
J --> K
K --> L
style G fill:#3a5ccc
style I fill:#7e4cc9
style K fill:#299764
```
## 1. The Rclone Source Pipeline (configured per knowledge database)
The rclone source pipeline pulls files from an external storage system into a knowledge database's data lake. It is
**configured per knowledge database from the UI**, not per deployment: one deployed `rclone_pipeline` code location
serves every database whose source is `rclone`, and nothing about a source lives in code or environment variables.
- **What it does**: Observes the remote of each database it fills, downloads new or updated files into that database's
bucket, removes files from the data lake that were deleted from the source, and announces every change to the
ingestion pipeline.
- **Key Assets**: `remote_files` (observable), `data_lake_files`, `removed_data_lake_files` in the
`rclone_source_to_datalake` group.
- **Backends**: the dialog offers Google Drive and AWS S3 (and S3-compatible), the two verified end to end, plus a local
path inside the rclone container where enabled. OneDrive / SharePoint, Azure Blob and SFTP are implemented but hidden
(see below).
### Two Axes of a Knowledge Database
A knowledge database carries two independent settings:
| Axis | Field on the database | Question it answers | Set by |
| ---------- | --------------------------------- | ---------------------------------- | ----------------------------------- |
| `ingestor` | `ingestor` + `configuration` | How are files processed (Stage 2) | Create dialog, fixed after creation |
| `source` | `source` + `source_configuration` | Where do files come from (Stage 1) | Create dialog, replaceable |
A database without a source is filled by manual upload. The source configuration stays replaceable because credentials
rotate; a change takes effect on the pipeline's next run.
### Giving a Database a Source
1. In the create-database dialog, pick a **Source**. The selector offers every source pipeline that is currently
deployed and has announced its form (`GET /knowledge/source-pipelines`).
2. Choose the **Storage backend** (`s3` or `drive`) and fill in the backend's options; only the fields of the selected
backend are shown. For a Google Drive service account, upload the JSON key file downloaded from the Google Cloud
console, and share the Drive folder with the `client_email` the dialog shows for it.
3. Set the **Root folder** inside the source and, optionally, include/exclude patterns in
[rclone filter syntax](https://rclone.org/filtering/) (for example `*.pdf`, `**/~$*`).
4. Save. The database is synced daily; the first observation creates a partition per file and the next one downloads
them.
Replacing or clearing the source later goes through `PUT /knowledge/databases/{database}/source`. Clearing only stops
the sync; the files already in the data lake stay until the database is deleted.
### What a Database Stores
`source_configuration` holds the `backend_type`, `root_path`, `include_patterns`, `exclude_patterns`, and one option
group per backend. The dialog offers only the backends verified end to end (`s3`, `drive`, and `local` where enabled);
the others remain configured and synced for databases that already use them, and through the API:
| `backend_type` | Source | Options |
| -------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `onedrive` | OneDrive, SharePoint (not offered in the dialog) | `client_id`, `client_secret`, `tenant`, `drive_id`, `drive_type`, `region`, or a `token` |
| `drive` | Google Drive | `client_id`, `client_secret`, `token` or `service_account_credentials` (uploaded key file), `root_folder_id` |
| `s3` | AWS S3, MinIO, S3-compatible | `provider`, `access_key_id`, `secret_access_key`, `region`, `endpoint` |
| `azureblob` | Azure Blob Storage (not offered in the dialog) | `account`, `key` or `sas_url`, `endpoint` |
| `sftp` | SFTP servers (not offered in the dialog) | `host`, `port`, `user`, `password` or `key_pem` |
| `local` | Path inside the rclone container | none; offered only where `RCLONE_LOCAL_SOURCE_ROOT` is set, and confined to that directory |
A SharePoint document library is `onedrive` with `drive_type=documentLibrary`. OAuth backends accept a pre-obtained
rclone `token` JSON instead of client credentials; OneDrive without a token uses client credentials.
Replacing a Drive key is safe while the new service account sees the same folder. A valid key whose account sees a
different or empty tree (for example no `root_folder_id` and only a shared folder) makes the next sync remove every
document it no longer lists.
Credentials are secrets: the API encrypts them with the platform's `AIHUB_CONFIG_ENCRYPTION_KEY` before storing them,
returns them masked, and keeps the stored value when a form is resubmitted with the mask. The pipeline decrypts them per
run with the same key.
### Generated Namespaces
Files land at `s3://{database}/{top-level folder}/…`, and the ingestion pipeline maps the first path segment to a
namespace, as it does for uploads (see [Default Data Mapping](#default-data-mapping)). Namespaces of a sourced database
are therefore generated from the folders under the root path.
::: warning Root-level files are skipped
Files directly in the chosen root folder have no folder to become a namespace and are not synced. The observation
reports how many were skipped. Move them into a folder or point the root one level up.
:::
::: warning Colliding folder names are skipped, not merged
Two top-level folders that sanitise to the same namespace (`hr docs` and `hr_docs` both become `hr_docs`) cannot share
it: a namespace stores one folder, and teardown acts on that folder only. The first folder keeps the name; the files of
the second get no partition. The ingestion observation reports them (`Skipped files (namespace collision)` count and a
`Skipped files` table naming both folders), and removal compares URIs only, so a collision never blocks ingestion or
removal for the rest of the database. Renaming the losing folder at the source repairs it.
:::
A sourced database has one owner of its content: it refuses manual upload, hand-made namespaces and manual document
deletion, because the next sync would re-create or remove them. The database can still be deleted as a whole.
### How a Synced File Reaches the Ingestion Pipeline
The source pipeline never writes to the document store or the vector store. After every written or removed file it
publishes a `SourceUpdatedEvent` on the owning ingestor's subject, the same event a browser upload triggers, so the
ingestion pipeline picks the change up within its sensor interval instead of at its next daily observation. The sync
itself runs on a daily schedule.
### Deployment
The platform ships the pipeline as the `rclone_pipeline` compose service (Dagster workspace entry `rclone_pipeline`),
next to the `rclone` daemon it drives over the RC API. Nothing per source is deployed; the service needs:
| Variable | Purpose |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `AIHUB_CONFIG_ENCRYPTION_KEY` | Decrypts stored credentials; must equal the API's key |
| `RCLONE_URL`, `RCLONE_RC_USER`, `RCLONE_RC_PASS` | Reach and authenticate against the rclone daemon |
| `RCLONE_PIPELINE_OBSERVE_JOB_HOUR` / `_MINUTE` | Time of the daily per-database sync |
| `RCLONE_PIPELINE_MAX_PARTITIONS` | Partitions added or removed per database per observation |
| `RCLONE_LOCAL_SOURCE_ROOT` | Optional: directory inside the rclone container that `local` sources may read; unset hides the backend |
The `rclone` container keeps no configuration file: the pipeline re-creates each database's remote from the stored
configuration on every run, so a credential change applies on the next run and a restarted daemon needs no operator
action. The container sits on the `backend` network (reached by the pipeline) and the `egress` network (reaches the
cloud providers); the pipeline itself talks to the daemon over `backend`.
::: warning Change the RC credentials in production
`RCLONE_RC_USER` / `RCLONE_RC_PASS` protect the daemon that holds every database's remote in memory. Set strong, unique
values in production deployments.
:::
### Extending the Source Pipeline
**Adding a backend** rclone supports but the form does not yet offer is a change in `packages/pipeline` and
`packages/core` only: one `Form` subclass with the backend's options (credentials as `Password` fields, or
`SecretFileInput` where the provider hands out a key file) and one field on `RcloneSyncConfig` in
`source_pipelines/rclone_sync_config.py`, the backend in `RcloneBackendType`, and labels in the
`lib/source_pipelines.*.yml` translations. Once verified end to end, add it to `_TESTED_BACKENDS` so the dialog offers
it. The registration sensor re-announces the form; the API and the UI need no change.
**A second source pipeline type** registers the way the rclone one does: a `SourcePipelineConfig` subclass declares its
form, and a `rclone_pipeline_definitions`-style factory with its own `source` token, display name and description wires
the registration sensor, the per-database schedule and the routed Stage-1 assets:
```python
from swiss_ai_hub.core.infrastructure import RclonePipelineSettings
from swiss_ai_hub.pipeline.util.rclone_pipeline_definitions_util import rclone_pipeline_definitions
defs = rclone_pipeline_definitions(settings=RclonePipelineSettings()) # the shipped app, source="rclone"
```
The `source` token must not collide with any ingestor token: both kinds of pipeline name their Dagster jobs after their
token, so the factory rejects a reserved id when the definitions are built. See ADR
`2026_09_09_source_pipelines_as_a_second_axis_of_a_knowledge_database`.
## 2. The Data Lake to Vector Store Pipeline
This is the core document ingestion pipeline. Use the `document_ingestion_pipeline_definitions` factory to process
documents from your S3 data lake into a vector store.
- **What it does**: Observes the S3 bucket of every knowledge database assigned to this ingestor, parses documents,
chunks them into nodes, optionally creates summary nodes, and stores the embeddings in Milvus. It also handles
document deletions, and the teardown of a whole database or folder.
- **Key Assets**: `observable_data_lake`, `documents`, `nodes`, `summary_nodes`, `removed_documents`.
### Usage Example
```python
from swiss_ai_hub.core.i18n import LocaleString
from swiss_ai_hub.core.infrastructure import DocumentIngestionPipelineSettings
from swiss_ai_hub.pipeline.util import document_ingestion_pipeline_definitions
defs = document_ingestion_pipeline_definitions(
ingestor="my_rag", # Databases assigned to this ingestor are served
display_name=LocaleString(en="My RAG"), # Shown in the create-database dialog
description=LocaleString(en="Tuned for my documents"),
settings=DocumentIngestionPipelineSettings() # Deployment defaults, from DOCUMENT_INGESTION_*
)
```
`settings` carries the text, embedding and vision models, the three enrichment switches and the observation schedule.
Those are deployment defaults, not fixed behaviour. The pipeline announces a configuration form pre-filled with them,
each knowledge database created for this ingestor chooses its own values in the create dialog, and the pipeline reads
those values per run. Settings you pass in code win over the `DOCUMENT_INGESTION_*` environment variables, both for the
form's pre-filled defaults and for databases that store no value of their own; the environment is read only when no
settings were passed. If you point the text model at a provider without image input, also set the vision model
(`DOCUMENT_INGESTION_VISION_MODEL`); it defaults to the text model, and nothing checks that the model can read images.
See [Building Pipelines](../index.en.md#making-your-pipeline-selectable-in-the-ui) for how to add a setting of your own.
## Default Data Mapping
The SDK uses a consistent naming convention to map your data lake structure to the underlying storage backends (Document
Store and Vector Store).
### Container/Bucket → Database/Collection
The top-level S3 bucket name is used as the primary identifier for your storage resources, providing strong data
isolation.
**Example:**
- **Data Lake Bucket**: `s3://hr-documents/`
- **Document Store DB**: `hr-documents`
- **Vector Store Collection**: `hr-documents`
### Directory → Namespace
Within a bucket, you can use directories to create logical separations, which map to **namespaces** within the vector
store. This allows for multi-tenancy or logical grouping within a single collection.
**Example:**
- **Data Lake Path**: `s3://hr-documents/onboarding/`
- **Vector Store Namespace**: `onboarding`
## Running and Combining Pipelines
To run a pipeline, save your definitions code (e.g., `my_pipeline.py`) and use the Dagster CLI.
```bash
# Start the Dagster UI and development server
dagster dev -f my_pipeline.py
```
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/3_building_pipelines/4_job_scheduling.md
title: " Job Scheduling"
description: ""
---
# Job Scheduling
Once your pipeline is defined, the next step is to automate its execution.
## The Hybrid Automation Strategy
Instead of running the entire heavy pipeline on a fixed schedule, we separate the "checking" from the "processing."
### 1. Scheduled Observation
A lightweight **job** runs on a fixed schedule (e.g., daily at 2 AM). Its only purpose is to execute the **observable
source asset** (e.g., `observable_data_lake`). This job doesn't process any documents; it simply checks the source
system (like S3 or SharePoint) for new or modified files and records their versions. This is the "pulse" of your
pipeline.
### 2. Change-Driven Processing
A **sensor** (`default_automation_sensor`) or an `AutomationCondition` on an asset constantly monitors the state of your
pipeline. When it detects that the observable asset has produced a new data version (because the scheduled job found a
change), it automatically triggers the downstream processing assets (like `documents` and `nodes`).
This approach is highly efficient because the resource-intensive document processing work only runs when there are
actual data changes.
## Implementation with SDK Factories
The `document_ingestion_pipeline_definitions` and `default_sharepoint_to_datalake_definitions` factories automatically
configure this entire automation setup for you. They create the necessary jobs, schedules, and sensors to implement the
hybrid strategy.
Here is how the components are assembled within a `Definitions` object:
```python
# This pattern is automatically configured by the SDK's default factories.
from swiss_ai_hub.pipeline.jobs.factory import observe_source_job
from swiss_ai_hub.pipeline.schedules.factory import daily_schedule_at
from swiss_ai_hub.pipeline.sensors.factory import default_automation_sensor
# A. A job is created specifically to run the observation asset.
# This is a lightweight, fast-running job.
observe_job = observe_source_job(
observable_asset=observable_data_lake,
source_location_name="my_company_docs",
)
# B. A schedule is attached to the observation job.
# This tells Dagster to run the check at a specific time.
observe_schedule = daily_schedule_at(
job=observe_job,
hour=2, # Run at 2 AM daily
minute=0,
)
# C. A sensor is created to trigger the actual processing.
# It watches for new versions created by the observe_job.
automation_sensor = default_automation_sensor(all_pipeline_assets)
# The factory bundles these into the final Definitions object.
defs = Definitions(
assets=all_pipeline_assets,
jobs=[observe_job],
schedules=[observe_schedule],
sensors=[automation_sensor],
# ...resources and executors
)
```
The downstream assets themselves use `AutomationCondition.eager()` to ensure they run as soon as an upstream change is
detected by the sensor.
```python
@graph_asset(
key=AssetKey(["production", "documents"]),
ins={"data_lake_file": AssetIn(key=AssetKey(["production", "data_lake"]))},
# This asset will run automatically when the sensor detects a change
# in the upstream 'data_lake' asset for a given partition.
automation_condition=AutomationCondition.eager(),
)
def production_documents(data_lake_file: DataLakeFile) -> RefDocDocument:
return process_document(data_lake_file)
```
## Next Steps
- [Pipeline Observation](../5_pipeline_observation/) for monitoring the health and performance of your automated
pipelines.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/3_building_pipelines/5_pipeline_observation.md
title: "Pipeline Observation"
description: ""
---
# Pipeline Observation
Effective monitoring and debugging are essential for maintaining reliable data processing pipelines. Swiss AI Hub
provides comprehensive observability tools to track pipeline execution, diagnose issues, and optimize performance.
## What you'll learn
This guide covers pipeline observation:
- **Monitoring**: Track pipeline execution and performance metrics
- **Debugging**: Diagnose and resolve pipeline failures
- **Tracing**: Understand data lineage and processing history
## Dagster UI monitoring
### Asset lineage and dependencies
The Dagster UI provides visual asset lineage tracking:
```python
# View asset dependencies and execution status
@graph_asset(
key=AssetKey(["documents"]),
ins={"data_lake_file": AssetIn(key=AssetKey(["data_lake"]))},
description="Process data lake files into RefDocs",
)
def documents(data_lake_file: DataLakeFile) -> RefDocDocument:
"""Asset with clear lineage tracking."""
return process_document(data_lake_file)
```
### Asset materialization metadata
Add rich metadata to track processing details:
```python
@op
def parse_document_with_metadata(
context: OpExecutionContext,
data_lake_file: DataLakeFile
) -> RefDocDocument:
"""Operation with comprehensive metadata logging."""
start_time = time.time()
# Process document
document = parse_document(data_lake_file)
processing_time = time.time() - start_time
# Add detailed metadata
context.add_output_metadata(
metadata={
"file_size_mb": data_lake_file.size / 1e6,
"processing_time_seconds": processing_time,
"document_pages": len(document.pages) if hasattr(document, 'pages') else 1,
"text_length": len(document.text),
"parser_version": "mineru-2.7",
"success_rate": 1.0,
"Table": MetadataValue.table(
records=[{
"metric": "file_size_mb",
"value": data_lake_file.size / 1e6
}, {
"metric": "processing_time",
"value": f"{processing_time:.2f}s"
}]
)
}
)
context.log.info(f"Processed document: {data_lake_file.name} in {processing_time:.2f}s")
return document
```
### Partition-level monitoring
Track processing status across dynamic partitions:
```python
@asset(
partitions_def=document_partitions,
automation_condition=AutomationCondition.eager(),
)
def partitioned_processing(context: AssetExecutionContext) -> ProcessingResult:
"""Monitor partition-level execution."""
partition_key = context.partition_key
context.log.info(f"Processing partition: {partition_key}")
# Add partition-specific metadata
context.add_output_metadata(
metadata={
"partition_key": partition_key,
"partition_timestamp": context.partition_time_window.start.isoformat(),
"processing_node": os.getenv("HOSTNAME", "unknown"),
}
)
return process_partition_data(partition_key)
```
## Next steps
- Explore `packages/pipeline/playground/` for complete observable pipeline examples
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes.md
title: "Building Processes"
description: ""
---
# Building Processes
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes/1_process_fundamentals.md
title: "Process Fundamentals"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes/2_delegation_patterns.md
title: "Delegation Patterns"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes/3_external_process_tools.md
title: "External Process Tools"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes/4_testing_processes.md
title: "Testing Processes"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes/5_production_deployment.md
title: "Production Deployment"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/4_building_processes/6_process_observation.md
title: "Production Observation"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/5_advanced_topics.md
title: "Advanced SDK Topics"
description: ""
---
# Advanced SDK Topics
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/5_advanced_topics/1_custom_semantic_events.md
title: "Custom Semantic Events"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/5_advanced_topics/2_state_management.md
title: "State Management"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/5_advanced_topics/3_performance_optimizations.md
title: "Performance Optimizations"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/5_advanced_topics/4_error_handling.md
title: "Error Handling"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/5_advanced_topics/5_extending_the_ui.md
title: "Extensibility and Customization"
description: ""
---
# Extensibility and Customization
The Swiss AI Hub suite interface is designed for extensibility, enabling organizations to add custom AI capabilities,
integrate proprietary systems, and tailor the platform to specific business requirements - all while maintaining the
unified suite experience and without modifying core platform code.
::: tip Licensing note
The extension model described here — custom services and your own components built *on* the platform — leaves your
service code under whatever license you choose; you are not modifying the platform itself. The bundled UI
(`packages/web`) is, however, licensed under **AGPL-3.0**. If you modify that UI itself and offer it as a network
service, AGPL-3.0 requires you to publish those UI modifications under the same license. See
[Why the backend and UI use different licenses](../../../4_ecosystem/3_certification/2_sdk_licensing/) and
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md) for the details.
:::
## Architectural Foundations for Extensibility
The suite's extensibility stems from deliberate architectural decisions that separate extension points from core
infrastructure, enabling organizations to add capabilities without forking the codebase or creating custom platform
versions.
**Plugin Architecture**: Services integrate with the suite through a well-defined controller pattern rather than direct
code integration. Organizations implementing custom services follow the same patterns as native services, ensuring their
extensions receive automatic integration with authentication, permissions, internationalization, and observability
infrastructure.
**Standard Integration Contracts**: The controller pattern defines clear contracts for service integration. Custom
services implement these contracts, declare their metadata (name, description, icon, permissions), and mount their API
endpoints. The suite automatically discovers and integrates conforming controllers without requiring core platform
modifications.
**Separation of Core and Extension**: The platform explicitly separates core infrastructure (authentication,
authorization, messaging, persistence) from service implementations. Extensions leverage core infrastructure without
modifying it, ensuring platform updates don't break custom services and custom services don't compromise core platform
stability.
**Version Compatibility**: The controller integration contract maintains backward compatibility across platform
versions. Services implemented against one platform version continue functioning when the platform updates, protecting
organizational investment in custom capabilities.
## Implementing Custom Services
Organizations can implement custom services that appear as first-class citizens in the suite interface,
indistinguishable from native capabilities.
**Controller Implementation**: Custom services implement a controller class inheriting from the platform's base
controller. This controller defines the service's API endpoints, permission requirements, and metadata. The
implementation follows standard FastAPI patterns familiar to Python developers.
**Frontend Component Development**: Services requiring custom user interfaces implement frontend components using the
same technology stack as the native interface - Nuxt 3, Vue 3, and PrimeVue. These components access the custom
controller's API endpoints through automatically generated TypeScript clients, ensuring type safety across the
frontend-backend boundary.
**Automatic Suite Integration**: When a custom controller is registered with the platform, it automatically appears in
the suite's dynamic service discovery. Users with appropriate permissions see the custom service in their sidebar
navigation alongside native services. The custom service's icon, name, and description integrate seamlessly into the
unified interface.
**Shared Infrastructure Access**: Custom services automatically gain access to platform infrastructure - NATS messaging
for event-driven communication, MongoDB persistence for data storage, authentication/authorization for security,
internationalization for multi-language support, and observability tooling for monitoring and tracing.
## Extension Use Cases
Organizations implement various types of custom services to address specific business requirements.
**Industry-Specific Agents**: A financial services organization might implement custom agents for regulatory compliance
analysis, financial modeling, or risk assessment. These agents integrate into the suite's agent service, appearing
alongside native agents with industry-specific workflows and knowledge integration.
**Proprietary System Integration**: Organizations can implement services that bridge the Swiss AI Hub with proprietary
enterprise systems - ERP systems, custom databases, legacy applications. These integration services might expose
specialized agents that interact with proprietary systems or provide monitoring interfaces for AI-driven automation
within those systems.
**Custom Analytics Dashboards**: Organizations with specific reporting or analytics requirements can implement custom
dashboard services that aggregate data from agents, processes, and knowledge systems, presenting organization-specific
metrics and visualizations.
**Specialized Workflows**: Process-heavy organizations might implement custom process management interfaces tailored to
specific workflow types - document approval workflows, compliance verification processes, multi-stage review procedures.
These custom interfaces leverage the platform's process automation infrastructure while presenting domain-specific
views.
**External AI Model Integration**: Organizations using proprietary or specialized AI models can implement custom model
integration services that expose these models through the suite, enabling agents to leverage organization-specific AI
capabilities alongside standard models.
## Extension Development Workflow
The platform provides comprehensive tooling and documentation supporting custom service development.
**Development Environment**: Organizations set up local development environments mirroring production deployments,
enabling custom service development and testing without affecting production systems. Docker Compose configurations
provide all required infrastructure (databases, message buses, observability tools) for local development.
**Code Generation**: The platform provides code generators that scaffold new services with correct structure,
boilerplate code, and integration patterns. Developers start with working service templates rather than building from
scratch, accelerating development and ensuring compliance with platform conventions.
**Testing Infrastructure**: Custom services leverage the same testing frameworks as native services. The platform
provides test runners that simulate the suite environment, enabling comprehensive testing of custom services before
deployment.
**Documentation Templates**: The platform includes documentation templates and examples demonstrating custom service
implementation, frontend component development, API design, and suite integration. These resources accelerate
development by providing working examples of common patterns.
## Deployment and Distribution
Custom services deploy alongside the native platform, becoming integral parts of organizational Swiss AI Hub
installations.
**Container Packaging**: Custom services package as Docker containers following platform conventions. These containers
deploy alongside native platform components, enabling independent scaling and version management.
**Configuration Management**: Custom services use the platform's configuration management system, reading settings from
environment variables and configuration files. This integration enables consistent configuration practices across native
and custom services.
**Deployment Orchestration**: Organizations extend platform deployment configurations (Docker Compose files, Kubernetes
manifests) to include custom services. Deployment tooling treats custom services identically to native services,
applying the same health checks, monitoring, and lifecycle management.
**Update Independence**: Custom services can update independently of the native platform (within version compatibility
guarantees). Organizations can deploy new custom service versions without requiring full platform updates, enabling
agile development of custom capabilities.
## Governance and Quality
While the platform enables extensibility, organizations maintain control over which custom services deploy and how they
integrate.
**Permission Control**: Custom services declare permission requirements like native services. Administrators control
which users access custom services through the same role and permission management interfaces used for native
capabilities.
**Quality Standards**: Organizations can establish quality gates for custom service deployment - code review
requirements, testing standards, security audits, performance benchmarks. The platform's extensibility doesn't mandate
lower standards for custom services.
**Service Registry**: Organizations maintain awareness of deployed custom services through the same monitoring and
management interfaces used for native services. Custom services report health, expose metrics, and generate audit logs
identically to native capabilities.
**Namespace Isolation**: Organizations can implement namespace isolation where custom services for different
organizational units don't interfere with each other. The permission system ensures appropriate access boundaries.
## Community and Ecosystem Potential
The extensibility architecture enables potential ecosystem development around the Swiss AI Hub platform.
**Shared Extensions**: Organizations might share custom services with industry peers facing similar requirements. A
custom service for regulatory compliance in Swiss banking could benefit multiple financial institutions, fostering
collaborative development.
**Partner Ecosystem**: Technology partners could develop custom services that integrate their solutions with the Swiss
Swiss AI Hub, creating a marketplace of complementary capabilities that organizations can deploy based on their needs.
**Innovation Acceleration**: By enabling custom service development, the platform allows organizations to innovate
rapidly in response to emerging requirements without waiting for native platform features. Successful custom services
might inform future native platform development.
**Knowledge Sharing**: The community of Swiss AI Hub users can share implementation patterns, best practices, and
reference architectures for common custom service types, accelerating the broader ecosystem's capability development.
## Strategic Value for Organizations
The suite's extensibility delivers significant strategic advantages for organizations investing in AI capabilities.
**Future-Proof Investment**: As AI technology evolves and new capabilities emerge, organizations can integrate them into
their Swiss AI Hub deployment through custom services. Today's platform investment remains relevant as technology
advances.
**Avoid Vendor Lock-In**: Organizations can integrate proprietary AI capabilities, custom models, or third-party
services alongside native capabilities. This flexibility prevents dependence on a single vendor's feature roadmap or
technology choices.
**Competitive Differentiation**: Organizations can implement AI capabilities that reflect their unique business
processes, industry requirements, or competitive strategies. The suite provides infrastructure while organizations
control differentiation.
**Incremental Investment**: Rather than massive custom development projects, organizations can implement focused custom
services addressing specific needs while leveraging native capabilities for standard requirements. This enables
incremental investment aligned with value delivery.
**Control Over Roadmap**: Organizations determine which custom capabilities to develop and when, rather than waiting for
vendor feature releases. Critical business requirements can be addressed immediately through custom development.
## Technical Considerations
Organizations planning custom service development should consider several technical factors.
**Development Skills**: Custom service development requires Python expertise for backend implementation and TypeScript/
Vue.js skills for frontend development. Organizations should ensure access to developers with these capabilities or
invest in training.
**Maintenance Burden**: Custom services require ongoing maintenance - bug fixes, security updates, compatibility with
platform evolution. Organizations should plan for long-term maintenance rather than treating custom services as one-time
development projects.
**Testing Requirements**: Comprehensive testing is essential for custom services to ensure they don't compromise
platform stability or security. Organizations should invest in testing infrastructure and practices appropriate for
their custom service portfolio.
**Documentation**: Custom services should be documented to the same standard as native capabilities, ensuring users
understand their purpose, capabilities, and usage patterns. This documentation burden should factor into development
planning.
This extensibility architecture ensures that the Swiss AI Hub suite provides a foundation for long-term AI capability
evolution, enabling organizations to invest confidently in the platform knowing they can adapt it to emerging
requirements without compromising the unified suite experience or requiring platform modifications that complicate
updates.
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/6_feature_overview.md
title: "Features"
description: ""
---
# :sparkles: Features
This chapter showcases the most important features that the SDK has to offer. As our platform evolves, this list
continuously grows with each release, reflecting our commitment to delivering cutting-edge capabilities for enterprise
AI.
::: info Living Documentation
This feature list is regularly updated. Check back often to discover new capabilities as we expand the platform's
functionality.
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/3_sdk/6_feature_overview/bot-in-the-loop.md
title: "Bot-in-the-Loop"
description: ""
---
# Bot-in-the-Loop :left_right_arrow: :100:
::: info **TL;DR - What is Bot-in-the-Loop?**
Bot-in-the-Loop enables AI agents to **seamlessly pause workflows and request human input via Slack channels**, then
automatically continue execution with the human response. This pattern bridges the gap between autonomous AI processing
and human expertise, allowing agents to handle complex decisions while maintaining full automation around human
involvement.
:::
## What is Bot-in-the-Loop and How Does It Work? :brain:
Bot-in-the-Loop represents a sophisticated **human-AI collaboration pattern** that enables AI agents to pause their
automated workflows at critical decision points and seamlessly integrate human expertise through structured Slack
interactions.
**Workflow Integration Architecture** allows AI agents to invoke Bot-in-the-Loop at any point in their execution. When
an agent encounters a decision requiring human input, approval, or expertise, it emits a `BotInTheLoopRequestEvent` that
automatically:
- Posts a formatted question to a designated Slack channel
- Maintains full workflow context and conversation threading
- Waits for human response without blocking other system operations
- Captures the response and automatically resumes agent execution
**Slack Channel Integration** provides the human interface through familiar collaboration tools. Experts respond
directly in Slack threads, where:
- Questions appear as structured, threaded messages
- Multiple experts can collaborate on responses
- Response attribution tracks who provided input
- Conversation history is maintained for audit and learning
**Event-Driven Orchestration** powers the entire interaction through the Swiss AI Hub's event system:
- `BotInTheLoopRequestEvent` pauses workflows and posts to Slack
- `BotInTheLoopResponseEvent` captures responses and resumes workflows
- Full context preservation ensures seamless continuation
- Error handling manages timeouts and failed responses
**Agent Workflow Integration** uses the simple `BotInTheLoop.invoke()` helper that makes integration trivial for agent
developers:
```python
# Pause workflow for human input
return BotInTheLoop.invoke(
user=current_user,
question="Should I proceed with this high-risk operation?",
slack_channel_id="C08MCK6LEBY"
)
```
**Key Technologies:**
- **NATS Event System** - Asynchronous message routing and workflow orchestration
- **Slack Bot Framework** - Channel integration with threading and attribution
- **Azure Bot Service** - Multi-channel bot connectivity and message processing
- **Event Store** - Persistent context and conversation tracking
- **Agent Workflow Engine** - Seamless integration with AI agent execution flows
## Why This is a Game-Changer for Your AI Strategy :trophy:
Bot-in-the-Loop transforms how organizations approach AI automation by solving the critical challenge of human-AI
collaboration at scale:
**🤝 Seamless Human-AI Collaboration**: AI agents can now naturally involve human experts without breaking automated
workflows. Complex decisions, approvals, and knowledge validation happen through familiar Slack interactions, while the
AI maintains full context and automatically continues processing with human input.
**⚡ Uninterrupted Automation**: Unlike traditional systems that require manual intervention points, Bot-in-the-Loop
maintains continuous automation around human involvement. Agents pause only when necessary, request specific input, and
immediately resume processing, maximizing both efficiency and human expertise utilization.
**🧠 Scalable Expert Knowledge Integration**: Subject matter experts can efficiently support multiple AI workflows
simultaneously through structured Slack interactions. One expert consultation can inform multiple agents, and their
knowledge is captured for organizational learning and future automation improvements.
**🛡️ Risk-Aware Decision Making**: Critical decisions that require human judgment - regulatory compliance, high-stakes
approvals, or complex interpretations - can be seamlessly integrated into otherwise automated processes. This ensures
both speed and safety in AI-powered workflows.
**📚 Organizational Knowledge Capture**: Every human response is captured with full context, creating an ever-growing
repository of expert decisions and knowledge. This enables continuous improvement of AI systems and builds institutional
knowledge that survives personnel changes.
::: details **Setting Up and Using Bot-in-the-Loop**
## Configuration Requirements
### Slack Bot Setup
1. **Azure Bot Service Configuration**: Ensure your Swiss AI Hub has Azure Bot Service integration configured
```bash
python packages/bot/setup_azure_bot.py \
--resource-group "your-resource-group" \
--bot-name "your-swiss-ai-hub-bot" \
--token-url "https://your-swiss-ai-hub-domain.com" \
--slack-token "xoxb-your-slack-oauth-token"
```
2. **Slack App Configuration**: Create a Slack App with required permissions
- **Bot Token Scopes**: `channels:read`, `chat:write`, `chat:write.public`, `users:read`
- **Event Subscriptions**: Subscribe to `message.channels` events
- **OAuth Installation**: Install the app to your workspace
3. **Channel Access**: Ensure your bot is added to the Slack channels where you want to post questions
```
/invite @YourAIHubBot
```
### Agent Integration Setup
1. **Import Bot-in-the-Loop Helper**: Add to your agent imports
```python
from swiss_ai_hub.core.events.agent.bitl import BotInTheLoop
```
2. **Configure Slack Channel IDs**: Identify your Slack channel IDs (format: `C08MCK6LEBY`)
```python
# Right-click channel → View channel details → Copy channel ID
EXPERT_CHANNEL_ID = "C08MCK6LEBY"
```
3. **NATS Event Distribution**: Ensure your Swiss AI Hub deployment has NATS messaging configured for event routing
## Usage Examples
### Basic Agent Integration
**Simple Decision Point:**
```python
class ApprovalAgent(Agent):
@step()
async def approval_step(
self,
event: RequestEvent,
run_context: RunContext
) -> BotInTheLoop.request:
user = await run_context.get("user")
return BotInTheLoop.invoke(
user=user,
question=f"Approve budget request for ${event.amount}?",
slack_channel_id="C08MCK6LEBY"
)
@step()
async def process_approval(
self,
response: BotInTheLoop.response
) -> ApprovedEvent | RejectedEvent:
if response.response.lower() in ["yes", "approved", "approve"]:
return ApprovedEvent(approver=response.responder.user_name)
return RejectedEvent(reason=response.response)
```
### Expert Consultation Workflow
**Knowledge-Seeking Pattern:**
```python
class ExpertConsultationAgent(Agent):
@step()
async def consult_expert(
self,
event: ComplexQuestionEvent,
agent_config: ConsultationConfig
) -> BotInTheLoop.request:
return BotInTheLoop.invoke(
user=event.user,
question=f"Expert input needed: {event.question}",
slack_channel_id=agent_config.expert_channel_id
)
@step()
async def process_expert_response(
self,
response: BotInTheLoop.response,
agent_config: ConsultationConfig
) -> ExpertAnswerEvent:
# Optionally save to knowledge base
if agent_config.save_to_knowledge_base:
await self.save_expert_knowledge(
question=response.request_event.question,
answer=response.response,
expert=response.responder.user_name
)
return ExpertAnswerEvent(
answer=response.response,
expert_name=response.responder.user_name,
confidence_score=self.assess_response_quality(response.response)
)
```
### Multi-Stage Approval Process
**Sequential Human Checkpoints:**
```python
class MultiStageApprovalAgent(Agent):
@step()
async def technical_review(
self,
event: SubmissionEvent
) -> BotInTheLoop.request:
return BotInTheLoop.invoke(
user=event.user,
question=f"Technical review: {event.proposal_summary}",
slack_channel_id="C08TECH123" # Technical team channel
)
@step()
async def business_approval(
self,
technical_response: BotInTheLoop.response
) -> BotInTheLoop.request:
return BotInTheLoop.invoke(
user=technical_response.request_event.user,
question=f"Business approval needed. Technical review: {technical_response.response}",
slack_channel_id="C08BIZ456" # Business team channel
)
@step()
async def final_decision(
self,
business_response: BotInTheLoop.response
) -> FinalDecisionEvent:
return FinalDecisionEvent(
approved=self.parse_approval(business_response.response),
technical_reviewer=business_response.request_event.request_event.responder.user_name,
business_approver=business_response.responder.user_name
)
```
## Available Capabilities
**Agent Workflow Integration:**
- **Simple Invocation**: One-line integration with `BotInTheLoop.invoke()`
- **Context Preservation**: Full workflow state maintained across human interactions
- **Response Processing**: Structured response handling with user attribution
- **Error Handling**: Timeout management and graceful failure handling
**Slack Channel Features:**
- **Threaded Conversations**: Responses are captured in organized threads
- **Multi-Expert Support**: Multiple experts can collaborate on responses
- **User Attribution**: Track who provided each response for accountability
- **Rich Formatting**: Support for Slack markdown and structured messages
**Event System Integration:**
- **Asynchronous Processing**: Non-blocking human interactions with parallel workflow support
- **Event Persistence**: Full conversation history stored for audit and replay
- **Cross-Agent Communication**: Responses can be shared between different agent workflows
- **Monitoring Integration**: Langfuse tracing and observability for human interaction patterns
## Security and Best Practices
**Security Considerations:**
- **Channel Access Control**: Restrict bot access to designated expert channels only
- **User Authentication**: Slack user identity is captured and attributed to responses
- **Audit Trails**: Complete conversation history maintained for compliance requirements
- **Sensitive Data Handling**: Questions should avoid exposing confidential information in channel messages
**Best Practices:**
- **Channel Organization**: Create dedicated channels for different types of expert consultation (technical, legal,
business)
- **Question Formatting**: Structure questions clearly with context and specific asks to get quality responses
- **Response Validation**: Implement basic validation of human responses before continuing workflows
- **Timeout Management**: Set appropriate expectations for response times and handle timeouts gracefully
- **Knowledge Capture**: Consider automatically saving valuable expert responses to knowledge bases
**Performance Optimization:**
- **Channel Distribution**: Distribute different question types across multiple channels to prevent expert overload
- **Batching**: For similar questions, consider batching requests to reduce expert interruption
- **Caching**: Cache common expert responses to reduce redundant questions
- **Escalation Paths**: Implement escalation when primary experts are unavailable
:::
## Getting Started
To implement Bot-in-the-Loop in your Swiss AI Hub deployment:
1. **Configure Slack Integration**: Set up Azure Bot Service with Slack connectivity and ensure your bot has access to
designated expert channels
2. **Identify Expert Channels**: Create or identify Slack channels where subject matter experts are available to respond
to agent questions
3. **Integrate in Agent Workflows**: Use the simple `BotInTheLoop.invoke()` pattern at decision points where human input
adds value to automated processes
For detailed agent integration patterns, see the
[Expert Agents documentation](../../../2_platform/5_agents/9_expert_coordinator_agent/) for real-world implementation
examples, and the Swiss AI Hub Agent Developer's Guide for comprehensive workflow integration instructions.
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem.md
title: "Part IV: The Ecosystem Journey"
description: ""
---
# Part IV: The Ecosystem Journey
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/1_contributing.md
title: "Contributing"
description: ""
---
# Contributing
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/1_contributing/1_contribution_guidelines.md
title: "Contribution Guidelines"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/1_contributing/2_arch_42_decision_records.md
title: "Arch42 Decision Records"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/1_contributing/3_testing_requirements.md
title: "Testing Requirements"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/1_contributing/4_documentation_standards.md
title: "Documentation Standards"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/1_contributing/5_pull_request_process.md
title: "Pull-Request Process"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/2_using_ai.md
title: "Using AI to Contribute"
description: ""
---
# Using AI to Contribute
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/2_using_ai/1_claude_code.md
title: "Core Terminology"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/2_using_ai/2_mcp_servers.md
title: "MCP Servers"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/2_using_ai/3_vibe_coding_guidelines.md
title: "Vibe Coding Guidelines"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/3_certification.md
title: "Certification"
description: ""
---
# Certification
@thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/3_certification/1_certification_path.md
title: "Certification Path"
description: ""
---
@thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/3_certification/2_sdk_licensing.md
title: "SDK Licensing"
description: ""
---
# Why the backend and UI use different licenses
Swiss AI Hub deliberately uses two open-source licenses for its two main components:
- **Backend** — Apache License 2.0
- **User interface** — GNU AGPL v3 (or later)
This split is intentional. It balances openness with the practical needs of organizations that build and deploy AI
agents on the platform. The authoritative, per-package breakdown lives in
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md); this page explains the reasoning behind it.
## Backend: Apache 2.0
The backend is where you define, configure, and run your agents, workflows, prompts, integrations, and business logic.
It is licensed under Apache 2.0 to give you maximum flexibility when deploying and extending the platform.
Many organizations consider their agents and agent logic to be proprietary intellectual property. Under a
network-copyleft license such as AGPLv3, operating a *modified* backend as a network service could require you to make
those modifications available under AGPL terms. In practice, that creates uncertainty around whether your agent
implementations and related business logic must be published.
Apache 2.0 removes that uncertainty: you can build and operate proprietary agents and backend extensions without any
concern that your implementations must be disclosed. As a permissive license it also grants explicit patent rights and
is well understood by enterprise legal teams.
This covers the platform runtime and the SDK you build on — the `core`, `agent`, `api`, `bot`, `pipeline`, and `process`
packages.
## User interface: AGPLv3
The UI is licensed under AGPLv3 to ensure that improvements to the user-facing application remain available to the
community. Organizations are free to use the UI, but if they modify the UI itself and provide it as a network service,
AGPLv3 requires those UI modifications to be shared under the same license.
This helps prevent a situation where the community maintains the UI while third parties create proprietary forks of the
interface without contributing their improvements back. The backup-and-restore orchestration service is licensed the
same way, for the same reason.
## The goal
The objective of this license split is to:
- Keep the core platform open and community-driven.
- Encourage contributions and the sharing of improvements to the UI.
- Allow organizations to build proprietary agents, workflows, and business-specific logic on top of the backend.
- Avoid creating licensing concerns around the disclosure of agent implementations.
In short, the backend remains permissively licensed to protect your ability to develop proprietary agent solutions,
while the UI uses a copyleft license to ensure that improvements to the user experience continue to benefit the broader
community.
::: tip Full per-package terms
This page explains the *why*. For the exact license that applies to each package — including the small number of
components distributed under other terms — see
[LICENSES.md](https://github.com/bbvch-ai/aihub-core/blob/main/LICENSES.md).
:::
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/3_certification/3_commercial_licensing.md
title: "Commercial Licensing"
description: ""
---
@thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/4_ecosystem/3_certification/4_support_and_training.md
title: "Support and Training"
description: ""
---
@thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references.md
title: "Part V: Reference & Resources"
description: ""
---
# Part V: Reference & Resources
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/1_api.md
title: "API Reference"
description: ""
---
# API Reference
@mhoegger @joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/1_api/1_rest_api.md
title: "REST & Sockets API"
description: ""
---
@thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/1_api/2_event_reference.md
title: "Event Reference"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/1_api/3_sdk_reference.md
title: "SDK Reference"
description: ""
---
@mhoegger
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/1_api/4_config_reference.md
title: "Config Reference"
description: ""
---
@mhoegger
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/2_troubleshooting.md
title: "Troubleshooting"
description: ""
---
# Troubleshooting
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/2_troubleshooting/1_common_issues.md
title: "Common Issues"
description: ""
---
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/2_troubleshooting/2_debugging_techniques.md
title: "Debugging Techniques"
description: ""
---
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/2_troubleshooting/3_performance_tuning.md
title: "Performance Tuning"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/2_troubleshooting/4_faq.md
title: "FAQ"
description: ""
---
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/3_glossary.md
title: "Glossary"
description: ""
---
# Glossary
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/3_glossary/1_core_terminology.md
title: "Common Issues"
description: ""
---
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/3_glossary/2_architecture_patterns.md
title: "Architecture Patterns"
description: ""
---
@joelbarmettlerUZH
---
---
url: https://docs.ai-hub.bbv.ch/docs/5_references/3_glossary/3_industry_terms.md
title: "Industry Terms"
description: ""
---
@mhoegger @mfundn @SkypeForBusiness @joelbarmettlerUZH @thommann
---
---
url: https://docs.ai-hub.bbv.ch/changelog.md
title: "Changelog"
description: ""
---
# Changelog
::: v-pre
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.316.3] - 2026-07-22 - Keycloak Service Updates for Enhanced Stability
### Changed
- 🔄 **Updated Keycloak services:** Upgraded the **Keycloak** container image to `26.5.5` and the **Keycloak-Config-CLI**
container image to `6.5.1-26.5.5` across all deployment environments, incorporating stability improvements and
potential security fixes.
______________________________________________________________________
## [v0.316.2] - 2026-07-22 - Enhanced Milvus Configuration for Improved Agent Compatibility
### Changed
- ⚙️ **Streamlined Milvus Environment Variable Propagation:** Milvus connection parameters (`MILVUS_URL`,
`MILVUS_DIMENSION`, `MILVUS_ROOT_PASSWORD`) are now explicitly included in the environment of additional services
across all Docker Compose configurations, ensuring more consistent and robust database connectivity for various
components.
- 📄 **Updated Documentation for Milvus Environment Variables:** The deployment guide's documentation has been revised to
reflect the expanded usage of Milvus environment variables, now indicating their relevance for agents like
`imap_agent`.
______________________________________________________________________
## [v0.316.1] - 2026-07-21 - Enhanced Development Workflow and SDK Consistency
### Added
- ✅ **Automated SDK Sync Verification:** A new GitHub Actions workflow has been introduced to automatically verify that
the committed frontend SDK clients (for both the main `@swiss-ai-hub/web` and `sysadmin-web` applications) are in sync
with their respective backend OpenAPI specifications. This crucial check helps prevent API/SDK drift and ensures
frontend clients always reflect the latest backend contract.
- ⚡️ **Efficient Offline OpenAPI Spec Generation:** The new workflow efficiently generates OpenAPI specifications
directly from backend ASGI applications without needing a running server, speeding up the verification process.
- ⚙️ **Proactive Developer Feedback:** Developers will now receive immediate feedback during pull requests if their
backend changes necessitate an SDK regeneration, promoting a more consistent and reliable development experience.
______________________________________________________________________
## [v0.316.0] - 2026-07-21 - Introducing the IMAP Agent for Enhanced Mail Automation
### Added
- 🦾 **New IMAP Agent**: Introduced a powerful new `ImapAgent` designed for non-conversational mail automation. This
agent can independently read and move emails, and automatically draft LLM-generated replies for incoming messages.
- 📄 **Docker Service Integration**: The new `ImapAgent` is fully integrated as a deployable Docker service, including
comprehensive environment variable configurations for seamless deployment and operation across various deployment
stages (build, latest, local, nightly, and GPU versions).
- ⚙️ **OpenTelemetry Support for IMAP Agent**: The `ImapAgent` now supports OpenTelemetry for enhanced observability and
tracing of its mail processing workflows.
### Changed
- 🔄 **Promoted IMAP Functionality**: The IMAP mail automation capabilities have been promoted from a playground example
to a core, production-ready `ImapAgent` within the main agent package, making it a formally recognized and deployable
component.
- 📚 **Updated Environment Variable Documentation**: The deployment guide documentation has been updated to include all
relevant environment variables for configuring the new `ImapAgent` and its associated services (LiteLLM, S3, MongoDB,
NATS, Redis).
______________________________________________________________________
## [v0.315.2] - 2026-07-21 - Unified Package Version Alignment
### Changed
- 🔄 **Core Platform Packages**: Synchronized all internal Python and JavaScript packages, along with their
inter-dependencies, to version `0.315.2` for improved consistency and stability across the platform.
______________________________________________________________________
## [v0.315.1] - 2026-07-21 - Enhanced Agent Management and Chat UI Responsiveness
### Added
- ✨ **Immediate Agent Model Updates:** Open WebUI now dynamically reflects agent creation, renaming, and deletion
instantly in the model picker, providing real-time feedback and eliminating the previous 60-second update delay.
- 🦾 **Introduced `AgentConfigChangeHook`:** A new internal mechanism has been implemented to leverage database signals
for immediate detection and propagation of agent configuration changes, powering the real-time updates in Open WebUI.
### Fixed
- 🐛 **Robust Agent Configuration Sync:** Resolved a critical issue where internal signal subscriptions could be
prematurely garbage-collected, ensuring reliable and persistent synchronization of agent configuration changes.
- 🖼️ **Improved Agent Deletion Flow:** Corrected a user interface bug by ensuring that when an agent is deleted from its
detail page, users are automatically navigated to the main agents list, preventing a disruptive 404 error toast.
______________________________________________________________________
## [v0.315.0] - 2026-07-21 - Major IMAP Agent Upgrade: Smart Drafting and Streamlined Workflows
### Added
- 🦾 **Independent Batch Email Drafting:** Introduced a powerful new capability for IMAP agents to draft replies for a
batch of messages autonomously. This feature is triggered by a new `DraftMailStartEvent` and can be scheduled and run
independently of the mail reading and moving workflow.
- ✨ **Configurable Drafting Settings:** A new `DraftEmailSettings` configuration group allows fine-grained control over
the drafting process, including the source folder for messages, batch size, the LLM model used for drafting, and the
prompt guiding the LLM.
- 🚀 **Flag-Based Deduplication:** The agent now prevents re-drafting the same message by utilizing a custom IMAP keyword
(`$AiHubDrafted`) or falling back to the `\Answered` flag, ensuring idempotency across runs.
- 📦 **New Events for Drafting:** Introduced `MailBatchDraftedEvent` to summarize the results of a batch drafting run,
and `DraftedReplyRef` to provide detailed references for each created draft.
- 📤 **Robust Draft Appending:** The IMAP client can now append `\Draft`-flagged messages to a configured drafts folder,
automatically resolving the exact server folder name (e.g., handling localized "[Gmail]/Drafts") and returning the
`APPENDUID` when supported.
- 🧵 **Enhanced Email Threading:** Mail parsing and `MailFetchedEvent` now include RFC `Message-ID`, `References`, and
`Reply-To` headers, enabling `ReplyComposer` to generate correctly threaded replies that appear nested in any mail
client.
- 🛠️ **Dedicated Reply Composition Logic:** A new `ReplyComposer` utility provides deterministic and idempotent
generation of reply envelopes, ensuring consistent subject lines (e.g., "Re: Subject") and proper threading.
### Changed
- 🔄 **IMAP Agent Workflow Separation:** The IMAP agent now supports two distinct and independent workflows: one for
reading and optionally moving mail, and another for batch drafting replies.
- ⚙️ **IMAP Configuration Restructuring:** The configuration for IMAP agents has been refactored, moving
drafting-related settings into the new `DraftEmailSettings` group. **Note: This is a breaking change to the config
shape; existing IMAP agent profiles must be recreated.**
- 📄 **Flexible Message Fetching:** The IMAP client's `fetch_message` method now accepts an optional folder argument,
allowing messages to be fetched from specific mailboxes beyond just the inbox.
- 🛡️ **Improved Folder Resolution:** IMAP client operations, such as moving mail and appending drafts, now include
robust folder resolution logic to ensure the correct mailbox is targeted, with fallbacks to special-use folders where
applicable.
### Removed
- 🗑️ **Consolidated `drafts_folder`:** The `drafts_folder` setting has been removed from the general `ImapClientConfig`
and integrated into the more specific `DraftEmailSettings`.
### Refactor
- 🧹 **Internal Workflow Streamlining:** The internal IMAP agent steps have been reorganized, separating the
`finish_after_move_step` from the new drafting logic to enhance modularity and clarity.
______________________________________________________________________
## [v0.314.0] - 2026-07-21 - Enhanced LLM Structured Output and Agent Self-Awareness
### Added
- 🦾 **Re-enabled Per-Agent Self-Awareness:** Agents can now detect and answer meta-questions about their capabilities
and workflows, providing a more intuitive and responsive user experience.
- 🧪 **New Integration Tests for Meta-Question Routing:** Comprehensive integration tests were added to validate that
meta-questions are correctly handled by self-awareness workflows and bypass irrelevant steps like retrieval.
### Changed
- ⚡️ **Optimized LLM Structured Output for Speed and Reliability:** Switched to `response_format` JSON schema as the
default mechanism for structured LLM outputs, particularly for reasoning models on Infomaniak. This update
significantly improves parsing reliability and drastically reduces latency by disabling unnecessary reasoning during
structured data extraction.
- ⚙️ **Universal `response_format` Support:** All chat models in the LiteLLM configuration now explicitly declare
support for `response_format` JSON schema, enabling consistent and high-performance structured output across the
board.
- 🔄 **Introduced Meta-Question Gating for Agent Workflows:** Core agent entry steps (e.g., memory retrieval, chat
history processing) are now gated by meta-question detection. This ensures that meta-questions are handled by
dedicated self-awareness workflows, preventing them from triggering the main agent pipeline.
- 📄 **Improved Resilience for Meta-Question Detection and Answering:** Meta-question detection now gracefully handles
transient API errors and malformed responses, preventing workflow failures. Additionally, meta-question answers
explicitly disable LLM reasoning to further reduce latency and improve responsiveness.
- 📝 **Updated Structured Output Handling in Conversation Metadata:** The internal logic for generating conversation
titles and follow-up questions has been adjusted to leverage the new `response_format` mechanism, no longer relying on
forced tool calls. (Note: Conversation metadata generation remains temporarily disabled.)
- 🛡️ **`McpReactAgent` Opts Out of Self-Awareness:** The `McpReactAgent` now explicitly bypasses self-awareness
features, streamlining its specific workflow.
### Refactor
- 🧹 **Superseded Prior Resilience Decision:** The architectural decision to enhance resilience for reasoning models has
been updated and superseded by a new document outlining the comprehensive adoption of `response_format` JSON schema
for structured LLM outputs.
______________________________________________________________________
## [v0.313.0] - 2026-07-17 - Streamlined Agent Configuration Import
### Added
- ✨ **Agent Instance Import Feature:** Introduced the ability to import agent configurations directly from JSON files,
making it easier to migrate or share agent setups between environments or users.
- 📄 **Robust Import Validation:** Implemented comprehensive validation for imported agent configuration files, including
checks for valid JSON format, correct structural integrity, presence of required fields, and support for the export
schema version.
- 🌐 **Localized Import Messages:** Added translated messages for the new agent import functionality, providing clear
feedback for various success and error states in multiple languages.
- 🚀 **Pre-fill Agent Creation from Import:** When a valid agent configuration is imported, the agent creation form will
now automatically pre-fill with the imported data, significantly speeding up the agent setup process.
- ⚙️ **Automatic Tenant ID Assignment:** Ensures that imported agent configurations are automatically assigned the
correct `tenant_id` and, if applicable, `org_memory.tenant_id`, for seamless integration into the current environment.
______________________________________________________________________
## [v0.312.1] - 2026-07-16 - Enhanced UI Clarity
### Added
- ✨ Added an **empty state message** to the Memory List table, providing clear feedback when no items are present.
______________________________________________________________________
## [v0.312.0] - 2026-07-16 - Enhanced IMAP Agent: Organize Your Inbox with Mail Movement
### Added
- ✨ **Mail Movement Capability for IMAP Agent:** Introduced the ability for the IMAP agent to automatically move fetched
emails from the inbox to a specified "Processed" folder, enhancing mail organization workflows.
- 📬 **New `MailMovedEvent`:** A dedicated event is now emitted when an email is successfully moved on the IMAP server,
providing clear visibility of mail organization actions in the event timeline.
- ⚙️ **Configurable Mail Organization:** Added new IMAP client configuration options, `enable_move` and
`processed_folder`, allowing users to enable and define the target folder for processed emails.
- 🦾 **Robust IMAP Message Movement Logic:** Implemented a sophisticated `move_message` method in the IMAP client that
intelligently utilizes the IMAP `MOVE` command when available, or falls back to `COPY` and `UID EXPUNGE` for reliable
and non-destructive message relocation.
### Changed
- 📄 **Expanded IMAP Agent Demonstrator:** The `imap_workflow` demonstrator agent has been updated to showcase the new
optional mail movement functionality, providing a practical example of organizing fetched emails.
- 📝 **Updated IMAP Agent Descriptions:** Descriptions for the IMAP agent now clearly reflect its expanded capabilities,
highlighting the ability to organize mail in addition to reading it.
- 🔑 **User Identity in IMAP Start Event:** The `ReadMailStartEvent` now includes an optional `user` identity field,
enabling more contextualized agent runs when triggered programmatically through the API.
______________________________________________________________________
## [v0.311.1] - 2026-07-16 - OpenTelemetry Cost Optimization and Observability Fixes
### Fixed
- 🐛 **Reduced OpenTelemetry Data Cardinality**: Implemented robust filtering for HTTP server metrics to prevent
unbounded, high-cardinality data (e.g., `http.server.duration`, `http.server.request.size`) from being sent to the
backend. This change significantly reduces observability costs and improves the signal-to-noise ratio of collected
telemetry data by stopping automatic FastAPI/ASGI instrumentation.
- ⚡️ **Optimized OpenTelemetry HTTP Header Capture**: Switched from capturing all HTTP request headers to an explicit,
bounded allowlist (`content-type`, `accept`, `accept-language`) for span attributes. This prevents high-cardinality
attributes like `user-agent` or `cookie` from inflating trace and metric costs.
- 🔄 **Preserved `http.route` Semantics**: Ensured that the `http.route` OpenTelemetry attribute correctly retains its
bounded route template value, while concrete URL paths are now recorded under `url.path`. This prevents metric and
label cardinality explosions caused by de-templating `http.route`.
- 🧪 **Added Regression Tests for OTel Optimizations**: Introduced new tests to safeguard against the future
re-introduction of high-cardinality OpenTelemetry data issues by verifying HTTP header capture limits and the behavior
of the `http.route` attribute.
______________________________________________________________________
## [v0.311.0] - 2026-07-15 - Agent Configuration Export Introduced
### Added
- ✨ **Empowered users to export agent configurations**, allowing them to download a JSON file containing the full setup
of an agent instance directly from the agent card or its dedicated configuration page.
- 📄 **Implemented a standardized export schema (version 1.0)** for agent configurations, ensuring consistent and
machine-readable data for easier backup and migration.
- 🌐 **Included localized labels** for the new "Export" button, providing a seamless experience for users across all
supported languages.
______________________________________________________________________
## [v0.310.6] - 2026-07-15 - Enhanced Document List Selection
### Added
- ✨ **Introduced "Select All" for Documents:** Users can now easily select all deletable documents in the document list
using a new "Select All" checkbox in the table header, streamlining bulk action workflows.
### Changed
- 🔄 **Improved Document Selection Logic:** The mechanism for managing selected documents has been refined, ensuring that
the "Select All" option accurately reflects and applies only to documents that are available for deletion.
### Fixed
- 🐛 **Cleared Selection on Document Deletion:** Documents are now immediately deselected from the list of checked items
after they have been initiated for individual deletion, preventing inconsistencies in the selection state.
______________________________________________________________________
## [v0.310.5] - 2026-07-15 - Improved Release Tagging Workflow
### Changed
- ⚙️ **Improved Release Workflow Accuracy:** The GitHub Actions workflow responsible for setting `latest` tags now
explicitly checks out the precise source tag, ensuring greater consistency and preventing potential discrepancies
during release operations.
______________________________________________________________________
## [v0.310.4] - 2026-07-15 - Enhanced Container Health Monitoring
### Added
- 🚀 **Introduced Docker Health Checks:** Added robust `HEALTHCHECK` instructions to both the main web application
(`web`) and system administration interface (`sysadmin-web`) Docker containers. This enhancement allows container
orchestrators to proactively monitor service availability, automatically detect unresponsive containers, and manage
their lifecycle more effectively, leading to improved application resilience and operational stability.
______________________________________________________________________
## [v0.310.3] - 2026-07-15 - Seamless RAG Figure Delivery to LLMs
### Added
- ✨ **Introduced RAG Figure Inlining:** Implemented a new mechanism to inline RAG figures (images) as Base64 directly at
the LiteLLM gateway, ensuring compatibility with LLM providers that cannot directly access internal S3 storage.
- 🖼️ **Configurable Image Inlining:** Added new environment variables (`RAG_IMAGE_INLINE_ENABLED`,
`RAG_IMAGE_INLINE_MAX_BYTES`) to control RAG image inlining behavior and define a maximum size, allowing for flexible
deployment configurations and cost management.
- 🔗 **Dedicated Internal S3 Endpoint:** Introduced `S3_STORAGE_INTERNAL_ENDPOINT` for signing presigned URLs
specifically for in-cluster services like the LiteLLM gateway, enhancing secure and efficient internal data transfer.
- 📄 **Architectural Decision Record:** Added a new ADR outlining the rationale, design, and consequences of the RAG
figure inlining solution.
### Changed
- 🔄 **Refined S3 Presigned URL Generation:** Updated the S3 anonymous file access service to support generating
presigned URLs tailored for either public or internal (in-cluster) access based on the RAG image inlining setting.
- 🔌 **LiteLLM Gateway Integration:** Configured the LiteLLM gateway to utilize a new custom callback, which
intelligently fetches RAG figures from internal storage and inlines them as Base64 into LLM prompts.
- 🎛️ **RAG Agent Image Handling:** Modified RAG agents to sign image URLs against the new internal S3 endpoint when
inlining is enabled, ensuring figures are correctly prepared for the LiteLLM gateway.
- ⚙️ **Deployment Configuration:** Updated Docker Compose and deployment scripts to include the new LiteLLM custom
callback and propagate relevant environment variables to the LiteLLM and RAG agent services.
- 📚 **Documentation:** Expanded the environment variables documentation to include details on
`RAG_IMAGE_INLINE_ENABLED`, `RAG_IMAGE_INLINE_MAX_BYTES`, and `S3_STORAGE_INTERNAL_ENDPOINT`.
______________________________________________________________________
## [v0.308.3] - 2026-07-15 - Platform Version Synchronization and Maintenance
### Changed
- 📦 **Platform-Wide Version Alignment:** All core packages and their inter-dependencies have been updated to `v0.308.3`
to ensure consistent versioning across the entire platform, improving overall stability and build predictability.
______________________________________________________________________
## [v0.310.2] - 2026-07-15 - Enhanced Release Automation and Flexibility
### Changed
- ✨ **Improved npm publishing for backports:** The `publish-npm` workflow now intelligently handles backport or hotfix
releases. When publishing a version older than the current `latest` on the npm registry, it will assign a
`release-X.Y` dist-tag instead of implicitly moving the `latest` tag backwards, preserving the `latest` tag for new
mainline releases.
- 🚀 **Flexible manual release tagging:** The `release-manual` workflow now supports a `source_ref` input, allowing users
to tag releases from any specified branch or commit (e.g., `hotfix/0.308`) rather than strictly the branch the
workflow is run from. This greatly improves the process for hotfixes and maintenance branches.
- 📄 **Enhanced release workflow visibility:** The run name for manual releases now includes the `source_ref` being
tagged, providing clearer context at a glance within GitHub Actions.
- ⚙️ **Streamlined manual release validation:** Updated internal logic to validate and correctly checkout the specified
`source_ref`, ensuring robust and accurate manual release operations.
______________________________________________________________________
## [v0.310.1] - 2026-07-15 - Streamlined Manual Release Process
### Added
- ✨ **Introduced Manual Release Workflow:** A new GitHub Actions workflow (`release-manual.yml`) has been added to
provide maintainers with enhanced control and flexibility over the release process. This workflow allows for the
manual creation of releases with a specified `vMAJOR.MINOR.PATCH` version tag from any arbitrary branch, which is
particularly useful for hotfixes or special-purpose releases. It fully automates critical steps such as changelog
generation, version bumping, and dispatching all downstream build, release, and publishing workflows (e.g., Docker
images, npm, PyPI), while ensuring release safety by preventing tag overwrites and keeping the source branch pristine.
______________________________________________________________________
## [v0.310.0] - 2026-07-15 - Agents Gain IMAP Read Capability with Secure S3 Attachment Handling
### Added
- ✨ **Introduced IMAP Read Capability for Agents**: Agents can now connect to IMAP inboxes, list unread messages, and
fetch selected messages along with their attachments. This foundational capability enables agents to interact with
email workflows.
- 🦾 **New IMAP Agent Demonstrator**: A new `ImapAgent` (non-conversational) has been added to the playground, showcasing
how to list unread mail and fetch messages with S3-referenced attachments.
- 📦 **Core IMAP Configuration and Event Types**: Added `ImapClientConfig` for configuring IMAP connections, and new
event types like `UnreadMailListedEvent`, `MailFetchedEvent`, `MailAttachmentRef`, and `UnreadMailSummary` for
structured communication of IMAP data.
- ☁️ **S3 Integration for Agent Attachments**: The agent runtime now includes native S3 client integration, allowing
mail attachments to be stored securely in S3 and referenced by `file_id` within agent events, preventing event bloat.
- 🔒 **Configurable Message Size Limits**: Implemented deployment-fixed caps for raw message size, body size, and
attachment size to protect agents from hostile or oversized emails and prevent exceeding message-size limits in the
audit trail and WebSocket streams.
- 🌍 **Internationalization for IMAP Features**: Added translations for IMAP configuration fields and new IMAP-related
events in German, English, French, and Italian.
- 📄 **Architectural Decision Record for IMAP**: A new ADR documents the design decisions behind the agent's IMAP read
capability, covering exposure, event handling, and attachment storage.
### Changed
- 🚀 **Agent Runtime Dependency on S3**: The agent runtime now depends on S3 for storing mail attachments, expanding its
infrastructure requirements.
- 📡 **IMAP Events Exposed via WebSocket**: `UnreadMailListedEvent` and `MailFetchedEvent` are now included in the
`DisplayEvents` type, making them visible in the frontend's event timeline via WebSocket.
- 🧩 **Filename Sanitization for Attachments**: Improved security by sanitizing attachment filenames to prevent path
traversal vulnerabilities.
### Security
- 🔑 **HTML Body Exclusion from Events**: The raw HTML body of fetched emails is deliberately *not* surfaced in
`MailFetchedEvent` (which is persisted and streamed to the frontend) to mitigate Cross-Site Scripting (XSS) risks from
hostile sender markup.
______________________________________________________________________
## [v0.308.2] - 2026-07-14 - Enhanced OpenWebUI Model Availability and Access
### Added
- ⚙️ **New `list_base_models` method:** Introduced a new client method in `OpenWebuiClient` to retrieve OpenWebUI
registry entries for raw provider models (those without a `base_model_id`), enabling more comprehensive model
management.
### Fixed
- 🐛 **Resolved OpenWebUI model access denials:** Fixed an issue where users were unable to access certain workspace
models in OpenWebUI, encountering "Model not found" errors even when the models were visible. This occurred when an
underlying raw LiteLLM model was inadvertently registered, creating a "shadowing" entry that blocked user access.
- 🛡️ **Automated removal of shadowing base models:** Implemented an automatic cleanup process during OpenWebUI model
synchronization to detect and remove these problematic "shadowing" base model registry entries, ensuring consistent
and correct user access to all provisioned models.
______________________________________________________________________
## [v0.308.1] - 2026-07-14 - Refined Function Registration and Default Status
### Changed
- ⚙️ **Adjusted Default Function Activation:** Modified the initial setup process to register core functions like
**`source_action.py`**, **`tracing_action.py`**, and **`memory_action.py`** as *inactive* by default, providing
administrators with greater control over initial system capabilities.
______________________________________________________________________
## [v0.309.4] - 2026-07-14 - Improved Event Context Resolution for Enhanced UI Navigation
### Added
- ✨ **New Thread Resolution API:** Introduced a dedicated API endpoint
(`/api/v1/active/events/agents/displays/{display_id}/thread`) allowing the UI to dynamically resolve the correct
conversation thread from a display ID, streamlining navigation to related traces, sources, or memories.
- 📄 **Thread Reference DTO:** Added a new `ThreadReference` data transfer object to structure the API responses for
thread resolution.
- 📚 **Core Persistence Support:** Implemented a new method within the `PersistedAgentEventEntity` to efficiently query
and retrieve the thread ID associated with any given display ID from the persistence layer.
- 🧪 **Robust API Testing:** Included comprehensive new tests for the `resolve_thread_for_display` API endpoint,
validating correct thread resolution across various scenarios, including AI-Hub Traceability Layer (AITL) delegation.
### Changed
- 🔄 **Synchronized Event ID Hashing:** Updated the `_str_to_object_id` function in OpenWebUI actions (memory, source,
tracing) to precisely match the hashing logic used by AI-Hub's event production, ensuring consistent `display_id`
generation for accurate event linking.
- 🚀 **Dynamic UI Panel Navigation:** Enhanced the OpenWebUI frontend to dynamically fetch the associated **thread ID**
using the new API when users click "show traces," "show sources," or "show memories," providing a more resilient and
decoupled approach to navigating contextual panels.
- 🗑️ **Optimized Cross-Window Communication:** Removed the direct `thread_id` from `postMessage` payloads originating
from OpenWebUI functions, as the frontend now handles its resolution, simplifying the data passed between the embedded
UI and the parent application.
### Refactor
- 🧹 **Standardized Error Messaging:** Consolidated the "access denied" error message for HTTP 403 responses within the
`EventController` into a reusable constant, improving consistency and maintainability.
______________________________________________________________________
## [v0.309.3] - 2026-07-13 - Deployment Configuration & Documentation Enhancements
### Changed
- ⚙️ **Standardized MongoDB Connection String:** Explicitly configured the `MONGO_CONNECTION_STRING` across all Docker
Compose deployment environments, ensuring consistent and clear database connectivity for all services.
- 📄 **Updated Environment Variable Documentation:** Ensured the `memory_writer_agent` is correctly listed in the
environment variables documentation, reflecting its operational dependencies on MongoDB, NATS, and Neo4j.
______________________________________________________________________
## [v0.309.2] - 2026-07-13 - Empowering Expert Agents with Advanced Memory Capabilities
### Added
- 🦾 **Enhanced Expert Agent Capabilities:** The `expert_asking_agent` and `expert_rag_agent` services are now fully
configured to integrate with **Mem0** for advanced LLM, embedding, and reranking functionalities, and **Neo4j** for
organizational memory graph storage, significantly boosting their intelligence and knowledge retention.
### Changed
- 📄 **Updated Environment Variable Documentation:** The documentation for environment variables has been updated to
reflect the expanded utilization of **Mem0** and **Neo4j** settings by the `expert_asking_agent` and
`expert_rag_agent`.
### Removed
- 🗑️ **Streamlined CI/CD Workflow:** An extraneous Docker login step has been removed from the `test-backup-e2e` GitHub
Actions workflow, contributing to a more efficient build process.
______________________________________________________________________
## [v0.309.1] - 2026-07-13 - Enhanced Memory Protection and Agent Isolation
### Added
- ✅ **New Tests for Memory Scoping Logic**: Dedicated unit tests were introduced to validate the correct implementation
and behavior of agent-specific memory isolation within the Mem0 service.
### Changed
- 🔐 **Improved Agent Memory Isolation**: User memories in Mem0 are now natively scoped to the writing agent during
reconciliation, preventing one agent from modifying or deleting another agent's user-specific data. Organization
memories continue to be tenant-shared as intended.
______________________________________________________________________
## [v0.309.0] - 2026-07-09 - UX Boost: Asynchronous User Memory & Graph Store Optimization
### Added
- 🦾 Introduced the **`MemoryWriterAgent`**, a new dedicated system agent for asynchronously persisting user memories,
significantly improving chat responsiveness by decoupling memory writes from the critical path.
- ✨ Implemented a **`discoverable` flag** on the base `Agent` class, enabling programmatic control over which agents
appear in the Admin UI.
- 💬 Added new **event types** (`MemoryStorageRequestedEvent`, `StoreUserMemoryRequestedEvent`, `MemoryStoredStopEvent`)
to orchestrate the new asynchronous memory storage workflow.
- ⚙️ Introduced a new configuration option, **`enable_async_memory_storage`**, for RAG and ExpertRAG agents, allowing
administrators to enable or disable asynchronous user memory persistence.
### Changed
- ⚡️ Drastically **improved chat user experience** by making user memory persistence asynchronous; the "thinking"
indicator now clears immediately after an agent answers, without waiting for memory storage.
- 🚀 **Optimized user memory save performance** by disabling the Neo4j graph store for user memory, reducing save latency
by approximately 73% (from ~66s to ~17.5s) per turn. Organization memory retains graph store capabilities.
- 🔄 Enhanced **cross-agent user memory sharing** by routing user memory queries through the vector store, allowing
agents to access all user memories regardless of the originating agent.
- 📈 Expanded **Langfuse tracing** to correctly attribute asynchronous `MemoryWriterAgent` runs to the original user
query and context.
- 📦 Updated **deployment configurations** to include the new `MemoryWriterAgent` service across all Docker Compose
variants.
### Refactor
- 🧹 Redesigned **`AgentMemory` architecture** to use separate, lazily-built Mem0 services for user and organization
memory, enabling distinct and optimized storage configurations for each.
- 🛠️ Modernized **Mem0 integration** to gracefully handle disabled graph stores, preventing errors and ensuring robust
memory operations when graph data is not present.
- ⚙️ Introduced an **`enable_graph` parameter** to `Mem0Settings.get_config` for fine-grained control over graph store
inclusion, enabling performance optimizations per memory type.
______________________________________________________________________
## [v0.308.0] - 2026-07-08 - Dynamic LLM Access and Robust Role Management
### Added
- ✨ **Dynamic LLM Model Discovery and Provisioning**: Introduced automatic discovery of LiteLLM chat models and their
provisioning as workspace models in OpenWebUI, allowing users to select and interact with available LLMs.
- 🦾 **"Use All Models" Access Preset**: Added a new access preset to quickly grant users permission to utilize all
available LLM models.
- 📂 **Model Capabilities in Access Editor**: Integrated a dedicated 'Models' category into the Role Access Rules editor,
enabling fine-grained control over model permissions.
### Fixed
- 🐛 **Reliable Role Access Updates**: Corrected the role update mechanism to ensure that changes to access rules trigger
proper re-synchronization of OpenWebUI access grants.
- 🔒 **Normalized Model Access Rules**: Addressed an issue where model names containing dots (e.g., `Kimi-K2.6`) were not
correctly normalized in access rules, preventing proper matching and access grants. This applies to both role and
tenant access rules.
- 🩹 **Persistent Access Change Hooks**: Fixed a regression where internal signal subscriptions for access changes were
prematurely garbage-collected, ensuring that OpenWebUI re-syncs are reliably triggered.
- 🔑 **Improved Cross-Tenant Role Isolation**: Resolved a bug that could cause access rules for roles with the same name
across different tenants to collide, ensuring each tenant's roles maintain their distinct access permissions.
### Changed
- 🔄 **Unified OpenWebUI Model Management**: OpenWebUI provisioning now seamlessly manages both AI-Hub agent models and
newly introduced LLM workspace models, each with distinct prefixes and tailored access grant computations.
- 🖼️ **Enhanced Role Access Editor UI**: The Role Access Rules editor now features a dedicated "Models" category,
providing a clearer and more intuitive interface for managing model-related permissions.
### Refactor
- 🧹 **Clearer OpenWebUI Model Prefixes**: Renamed the OpenWebUI model prefix for agents (`AIHUB_MODEL_PREFIX` to
`AIHUB_AGENT_PREFIX`) to improve clarity and distinguish it from the new prefix used for LLM models.
______________________________________________________________________
## [v0.307.1] - 2026-07-07 - Seamless Multi-Agent Conversations: Dedicated Thread Contexts
### Fixed
- 🐛 **Multi-Agent Chat Context Interference**: Resolved an issue where multiple agents participating in the same chat
session could inadvertently share or overwrite each other's conversation context, leading to incorrect or mixed-up
responses.
- 🐛 **Agent-Specific Event Routing**: Corrected the internal event distribution logic to ensure messages are now
precisely routed to the intended agent instance within a chat, preventing cross-agent communication mix-ups.
### Added
- ✨ **Multi-Agent Routing Tests**: Introduced new comprehensive tests to validate that distinct agents in a shared chat
thread correctly maintain their separate contexts and respond independently.
### Changed
- 🔄 **Thread ID Salting for Agents/Models**: Updated the ID generation mechanism for chat threads to include agent or
model identifiers as a "salt." This ensures unique thread IDs for each agent's or model's interaction within a single
chat session, effectively isolating their operational context.
- ⚡️ **Targeted Event Distribution**: Modified the core event distributor to explicitly direct events to a specific
`target_agent` when provided, enhancing precision and control over agent interactions and preventing unintended event
broadcasts.
______________________________________________________________________
## [v0.307.0] - 2026-07-07 - Enhanced Model Access Control and Granular Permissions
### Added
- ✨ **New Model Permission Rules**: Introduced canonical permission rule builders and normalization logic within
`AccessChecker` to precisely define and manage access to individual models and model capabilities.
- 🚀 **Centralized Model Access Guard**: Added new `_assert_model_access` and `_has_model_access` helper methods to
`OpenaiService`, ensuring all model invocations enforce user permissions consistently at the service layer.
- 🧪 **Comprehensive Model Access Tests**: Added extensive unit tests for both `ModelService` and `OpenaiService` to
validate the new granular model and assistant access control mechanisms.
### Security
- 🔑 **Granular Model Access Enforcement**: Implemented strict access control checks across all OpenAI API service
methods (Chat Completions, Embeddings, Speech-to-Text, Text-to-Speech, Image Generation) to ensure users only access
models for which they have explicit permissions.
- 🔐 **Filtered Model Listings**: The `/v1/models` and `ModelService` listing endpoints now filter available models based
on the requesting user's granted permissions, only showing models they are authorized to use.
- 🛡️ **Improved Assistant Access Logic**: Refined the `chat_completion_with_assistants` and `get_model_with_assistants`
endpoints to correctly differentiate and apply access checks for both models and assistants, raising `403 Forbidden`
for unauthorized access.
- 🚫 **Prevented Permission Masking**: Modified model lookup fallback logic in `OpenaiService` so that only
`404 Not Found` errors will trigger a search for an assistant; any other error, such as `403 Forbidden`, is now
propagated directly.
### Refactor
- 🧹 **Centralized Access Control Logic**: Moved model and assistant access validation logic from the `OpenaiController`
to the `OpenaiService`, ensuring a single source of truth for permission checks and simplifying controller logic.
______________________________________________________________________
## [v0.306.2] - 2026-07-06 - Refined LLM Parameter Defaults
### Changed
- ⚙️ **LLM parameter default value:** Adjusted the default value for a specific LLM parameter (e.g., temperature) from
`0.1` to `0.0` to refine model configuration defaults and potentially promote more deterministic initial outputs.
______________________________________________________________________
## [v0.306.1] - 2026-07-02 - Improved Agent Form Reliability
### Fixed
- 🐛 **Agent configuration forms** now reliably preserve unsaved edits, preventing data loss even during background data
refreshes or transient loading states.
- 🧩 **Agent creation forms** no longer reset user input when the selected agent class data is refreshed, ensuring a
smoother creation process.
- 🔄 **Enhanced form stability** by preventing automatic refetches of agent class and instance data on window focus,
safeguarding ongoing user input.
______________________________________________________________________
## [v0.306.0] - 2026-07-02 - Major Upgrade: Secure Open Terminal for OpenWebUI Code Interpreter
### Added
- 🦾 **Open Terminal Service**: Introduced a new `open-terminal` service dedicated to sandboxed code execution for
OpenWebUI's plain LLM models. This new service offers per-user isolation and downloadable file output.
- 🔑 **`OPEN_TERMINAL_API_KEY`**: Added a new environment variable for authenticating OpenWebUI's connections to the Open
Terminal sandbox, enhancing security.
- 🌐 **`code-sandbox` Network Zone**: Established a new, isolated Docker network specifically for the `open-terminal`
sandbox and its direct callers (OpenWebUI), preventing lateral reach to other backend or data services.
- 📄 **Architectural Decision Record**: A detailed ADR (`2026_06_22_openwebui_code_execution_open_terminal.md`) was added
to document the architectural shift from Jupyter to Open Terminal, outlining the rationale, decision, and
consequences.
- ⚙️ **Open Terminal Dockerfile & Build Process**: Added a custom Dockerfile for `open-terminal` to include essential
document-processing libraries (reportlab, fpdf2) and integrated its build and push into the deployment Makefile.
### Changed
- 🔄 **OpenWebUI Code Execution Backend**: OpenWebUI now exclusively uses the new `open-terminal` service for
model-driven code execution, replacing the previous Jupyter integration.
- 📄 **Jupyter Role Clarification**: The Jupyter service is now explicitly designated as "retained in stack but no longer
the OpenWebUI code path," signifying its deprecation for this use case.
- 🗺️ **Network Topology**: Updated the system's network architecture from five to six isolated Docker networks to
incorporate the new `code-sandbox` zone, enhancing overall system isolation.
- 📚 **Documentation Updates**: Comprehensive revisions across the project documentation (skills, README, architecture,
deployment guides, environment variables) to reflect the integration of Open Terminal, its features, and the updated
network and deployment model.
- 🚦 **OpenWebUI Service Dependencies**: Changed OpenWebUI's health check dependency from `jupyter` to the new
`open-terminal` service.
### Security
- 🔒 **Enhanced Code Execution Isolation**: Arbitrary user-submitted code executed via Open Terminal now runs in a
dedicated `code-sandbox` network, which is internal (no outbound internet in non-dev stages) and only allows
communication with its direct callers, significantly reducing the blast radius in case of a sandbox escape.
- 🛡️ **Per-User Sandbox Isolation**: The Open Terminal service is configured for multi-user support, providing separate
Linux accounts and home directories for each user, improving data privacy and preventing users from accessing each
other's files within the sandbox.
______________________________________________________________________
## [v0.305.8] - 2026-07-01 - Enhanced Mem0 Milvus Filtering
### Added
- ✨ Introduced **`PatchedMilvusDB`** to extend the filtering capabilities of mem0's Milvus vector store, enabling more
complex query operations.
- 🧪 Added comprehensive **unit tests** for `PatchedMilvusDB` to ensure accurate and robust generation of Milvus query
filters, especially for advanced operators.
### Fixed
- 🐛 Resolved an issue where **`mem0`'s MilvusDB vector store** failed to correctly process advanced metadata filters,
particularly the `in` operator, which led to Milvus query parsing errors.
### Changed
- 🔄 The **`Mem0Service`** now integrates the `PatchedMilvusDB` wrapper for its internal Milvus vector store,
transparently applying the enhanced filter logic.
______________________________________________________________________
## [v0.305.7] - 2026-07-01 - Streamlined Dataset Details UI
### Removed
- 🗑️ **Dataset Details Page:** Removed an unnecessary `ConfirmPopup` component from the dataset details view,
simplifying the UI.
______________________________________________________________________
## [v0.305.6] - 2026-06-30 - Enhanced Document Processing and User Experience
### Added
- ✨ **Enhanced Document Page Counting:** The MarkItDown loader now accurately determines the page count for Microsoft
Word (.docx), PowerPoint (.pptx), and Excel (.xlsx) files, providing more precise document metadata.
- 🚀 **Implemented File Upload Progress Tracking:** Users will now see real-time progress indicators during document
uploads, improving visibility and user experience, especially for larger files.
______________________________________________________________________
## [v0.305.5] - 2026-06-30 - Enhanced CI/CD Workflow Resilience
### Fixed
- 🐛 **CI Workflow Failures:** Prevented potential CI/CD pipeline failures during Docker image pre-pull login by ensuring
that an expired or absent GitHub token no longer causes the job to fail, significantly improving overall workflow
stability.
### Changed
- 🔄 **Best-Effort Docker Login:** Modified the `ghcr.io` Docker login process in CI workflows to be best-effort; if
authentication fails (e.g., due to an expired token), the system will now proceed with anonymous pulls for public
images, guaranteeing job continuity.
- 📄 **Internal Documentation:** Updated internal documentation for GitHub Actions (`CLAUDE.md`) to explicitly reflect
the new best-effort login behavior and its rationale, providing clearer guidance for pipeline operations.
______________________________________________________________________
## [v0.305.4] - 2026-06-30 - Improved Agent Configuration Stability
### Fixed
- 🐛 **Agent ID Immutability:** Corrected an issue where changes to an agent's **agent ID** during an update could lead
to silent failures in real-time communication (SSE). The system now explicitly ensures the agent ID remains immutable,
aligning with the instance key and preventing chat completion issues.
- 🖼️ **UI Enhancement:** Disabled the **agent ID** field in the agent configuration form on the web interface, clearly
indicating its immutable nature and preventing users from inadvertently attempting to change it when editing an
existing agent.
______________________________________________________________________
## [v0.305.3] - 2026-06-29 - Fortifying AI Agents Against Flaky LLM Structured Output
### Added
- 🦾 **Introduced `ResilientOpenAILike` for Robust LLM Interactions**: A new wrapper for `OpenAILike` models that
automatically retries structured prediction calls multiple times. This significantly improves the reliability of
agents interacting with Large Language Models (LLMs) that may intermittently produce malformed structured output.
- 📄 **New Architecture Decision Record (ADR) on Reasoning Model Resilience**: Documented the challenges with structured
output from specific reasoning models (Kimi-K2.6, Qwen3.5) on Infomaniak and detailed the architectural decisions to
mitigate these issues, including the adoption of plain-text verdicts and retry mechanisms.
- ⚡️ **New `text_verdict` Utility Module**: A dedicated module providing helpers for requesting and parsing plain-text
verdicts from LLMs, which is now used by several guard and detection functions for improved reliability and speed.
### Changed
- 🔄 **Relevance Guards Now Use Plain-Text Verdicts**: The `agent_description_guard`, `few_shot_guard`, and
`context_sufficient_guard` have been converted from expecting structured JSON output to requesting and parsing simple
plain-text verdicts (e.g., `ALLOW`/`BLOCK`, `SUFFICIENT`/`INSUFFICIENT`). This change dramatically increases their
reliability with reasoning models.
- 🚀 **Faster and More Reliable Meta-Question Detection**: The `detect_meta_question` function now uses a streamlined,
single-token plain-text classification (e.g., `NORMAL`, `META_IDENTITY`) with reasoning disabled, reducing latency
from ~4-5 seconds to sub-second on reasoning models without compromising accuracy.
- 🛡️ **Guards Fail Open on Unrecognizable Responses**: Relevance guards (`agent_description_guard`, `few_shot_guard`,
`context_sufficient_guard`) now gracefully degrade by accepting a request if the LLM returns an unparseable verdict,
preventing agent crashes and improving user experience.
- 💬 **Strict System Message Placement for Qwen3.5 Compatibility**: The `do_answer_meta_question` and `RAGAgent`'s
guard-reject branch now ensure that system messages always lead the message list, resolving a
`400 - System message must be at the beginning` error with Qwen3.5.
- ⚙️ **Centralized Retry Mechanism for All Structured Predictions**: All structured prediction calls are now routed
through the new `ResilientOpenAILike` wrapper by default, providing a single point of failure mitigation for all
agents without requiring individual call-site changes.
### Fixed
- 🐛 **Resolved Agent Crashes Due to Flaky LLM Structured Output**: Addressed a critical issue where agents would crash
when reasoning models like Kimi-K2.6 or Qwen3.5 failed to produce reliably parseable structured output (e.g., empty
tool calls, malformed JSON).
- 🔒 **Prevented Sensitive Information Leaks in Guard Logs**: Guard-failure logs no longer include raw model output,
queries, or retrieved context, enhancing privacy and security.
______________________________________________________________________
## [v0.305.2] - 2026-06-29 - Enhanced Stability and Reliability
### Fixed
- 🐛 **Resolved infinite render loop** in the `DynamicConfiguration` component, preventing UI freezes and ensuring stable
form interaction after data updates.
- ⏱️ **Improved `FewShotAgent` test reliability** by extending the test runner's delay, preventing premature test
termination and accommodating longer execution times, especially in self-hosted LLM environments.
______________________________________________________________________
## [v0.305.1] - 2026-06-29 - Enhanced Form Data Stability
### Fixed
- 🐛 **Resolved dynamic form data mutation issues:** Implemented deep cloning during data hydration in the
`DynamicConfiguration` component. This prevents FormKit's write-backs from inadvertently mutating shared cached
objects (e.g., from Pinia-Colada) and eliminates potential infinite reactivity loops, leading to more stable and
predictable form behavior.
______________________________________________________________________
## [v0.305.0] - 2026-06-26 - Simplifying Agent Workflows: Meta-Question Detection Removed
### Removed
- 🗑️ **Meta-question detection and answering functionality** has been completely removed from all agents (Expert RAG,
Few-Shot, LLM Wrapping, MCP React, Namespace Selection, and RAG agents). This simplifies their internal architecture
and streamlines execution by removing the initial classification step.
- 🗑️ **Associated event types and internal logic** related to meta-question handling, such as
`MetaQuestionDetectedEvent` and `NotAMetaQuestionEvent`, have been deprecated from core agent events and the
self-awareness module.
- 🗑️ **Dedicated test infrastructure** for meta-question routing, including specific feature scenarios and integration
tests, has been removed in alignment with the deprecation of this functionality.
### Refactor
- 🧹 **Agent preconditions** for various steps (e.g., memory retrieval, chat history management) have been simplified by
removing their dependency on meta-question detection logic, leading to more direct and efficient workflow execution.
______________________________________________________________________
## [v0.304.1] - 2026-06-26 - Temporary Adjustment to Agent Metadata Features
### Changed
- 🔄 **Conversation Metadata Generation Temporarily Disabled:** To enable further investigation and refinement, the
automatic generation of conversation titles and follow-up questions has been temporarily paused across `RAGAgent`,
`ExpertRAGAgent`, `FewShotAgent`, `LLMWrappingAgent`, and `McpReactAgent`. This aims to ensure a more robust and
reliable agent experience in future iterations.
______________________________________________________________________
## [v0.304.0] - 2026-06-25 - Empowering Access Management: Introducing the Dynamic Capability Catalog
### Added
- ✨ **Dynamic Access Capability Catalog**: Introduced a powerful new system that generates a human-readable catalog of
grantable capabilities directly from API endpoint permissions at runtime. This ensures that the displayed permissions
always match what the system enforces, eliminating drift and simplifying authorization management for operators.
- 🔑 **Access Presets**: Added a curated library of common access rule combinations (presets) to facilitate one-click
assignment of broad permissions like "Use everything" or "Administer all agents."
- 🦾 **`@access_catalog_entry` Decorator**: New decorator for API controllers, allowing endpoints to easily opt into the
access capability catalog by providing localized labels and descriptions. The actual access rule is derived from the
endpoint's `user_with_permission` guard, making divergence structurally impossible.
- ⚙️ **`AIHUB_INTERNAL_API_BASE_URL` Configuration**: Introduced a new environment variable and Docker Compose
configuration to specify the internal base URL of the main platform API. This enables server-to-server communication,
crucial for the sysadmin plane to proxy the full access capability catalog.
- 🧭 **Canonical Access Rule Builders**: Added static helper methods (`process_user_rule`, `service_admin_rule`, etc.) to
`AccessChecker` for consistently building common permission templates, improving clarity and reducing errors.
- 📄 **New Architecture Decision Record (ADR)**: Documented the rationale and design behind the "Runtime-Derived Access
Capability Catalog" to provide comprehensive context on this significant feature.
- 🌐 **Comprehensive Localization for Access Management**: Added extensive internationalization (i18n) support for the
new access capabilities and presets, ensuring a localized experience for role and tenant-ceiling editors.
- 🧪 **Dedicated Access Capability Tests**: Introduced a new suite of unit and API tests to thoroughly validate the
functionality and security of the access capability catalog, including sysadmin and tenant ceiling considerations.
### Changed
- 🔄 **Redesigned Role and User Access Views**: The UI for managing roles and viewing user access now leverages the new
dynamic capability catalog, providing a detailed, hierarchical, and interactive representation of permissions instead
of a flat list of rules.
- 🖼️ **User Access Information**: The `UserWithAccessDTO` now includes the user's resolved `access_rules` and utilizes
the new `AccessCatalogService` to provide a more granular and accurate overview of effective access.
- 📚 **Documentation Updates**: Clarified guidelines for creating epics in the `write-issue` skill documentation and
updated `CLAUDE.md` regarding generated files, reflecting recent development practices.
- 📝 **Agent, Process, Knowledge, and Memory Endpoints**: Existing controller methods across various services (e.g.,
Agent, Process, Knowledge, Organization Memory, User Memory) have been updated with `@access_catalog_entry` decorators
to expose their operations in the new access capability catalog.
### Refactor
- 🧹 **Unified User Access Logic**: Refactored the internal logic for building user access information, moving it into a
dedicated `AccessCatalogService` to centralize and improve the consistency of access evaluation across different UI
components.
- ⚙️ **Core AccessChecker Enhancements**: Updated `AccessChecker` to use new canonical rule-building methods, improving
maintainability and consistency in permission checks.
______________________________________________________________________
## [v0.303.1] - 2026-06-24 - Superuser Configuration Streamlining
### Changed
- 📄 **Clarified Superuser Account Configuration:** The platform's superuser account now consistently uses
`SUPERUSER_EMAIL` as its Keycloak username, aligning with Keycloak's `registrationEmailAsUsername` enforcement for
login. The `SUPERUSER_USERNAME` variable has been re-designated as a log-only label for internal tracking, removing
its previous role in setting the Keycloak username. This update simplifies superuser provisioning and enhances clarity
in documentation across deployment guides and access control sections.
______________________________________________________________________
## [v0.303.0] - 2026-06-24 - Enhanced Image Generation Capabilities with Cloud Integration
### Added
- 🖼️ **New Cloud Image Generation Model:** Integrated the `image-generation/flux` model, enabling cloud-based image
generation capabilities via the Swiss LLM Cloud.
- ⚙️ **Swiss LLM Cloud Image API Configuration:** Added new environment variables (`SWISS_LLM_CLOUD_IMAGE_API_BASE_URL`,
`SWISS_LLM_CLOUD_IMAGE_API_KEY`) to configure access to the new image generation service.
### Changed
- 🚀 **Image Generation Model Prioritization:** Updated Docker Compose configurations to prioritize the new cloud-based
`image-generation/flux` model for non-GPU environments.
- 📝 **Image Prompt Expansion Logic:** Disabled LLM-powered prompt expansion for image generation when using the `flux`
model to accommodate its prompt length restrictions, ensuring raw prompts are passed directly.
### Removed
- 🗑️ **GPU-Specific Image Generation Model Placeholder:** Removed the generic `IMAGE_GENERATION_MODEL` setting from GPU
deployment configurations, streamlining image generation to utilize the new cloud model for non-GPU stages while GPU
stages await dedicated local integration.
______________________________________________________________________
## [v0.302.0] - 2026-06-24 - Smarter Conversations: Agents Now Generate Titles and Follow-up Questions
### Added
- 🦾 **Agent-Generated Conversation Metadata:** Agents now autonomously generate concise **conversation titles** (once
per thread, when a clear topic emerges) and **suggested follow-up questions** (after each response), enriching the
user experience by providing more contextual chat summaries and interaction prompts.
- 📄 **Architectural Decision Record for Conversation Metadata:** A new ADR
(`2026_06_18_conversation_metadata_as_explicit_per_agent_steps.md`) has been added, detailing the design and
implementation strategy for agents producing conversation titles and follow-up questions.
- ✨ **New Display Events for Conversation Metadata:** Introduced `ConversationTitleEvent` and `FollowUpQuestionsEvent`
to transmit agent-generated metadata, ensuring these insights are seamlessly communicated through the event pipeline.
- 🚀 **Integration Across Key Agents:** The new conversation metadata generation has been integrated into several core
conversational agents, including **RAGAgent**, **ExpertRAGAgent**, **LLMWrappingAgent**, **FewShotAgent**, and
**McpReactAgent**, making these features widely available.
- 🌍 **Multilingual Prompts and UI Elements for Metadata:** Added comprehensive internationalization support for the new
conversation metadata features, including prompts for title and follow-up question generation, and localized display
names for the events in the UI.
- 🖼️ **Web UI Components for Conversation Metadata:** New web UI components (`ConversationTitleEvent.vue`,
`FollowUpQuestionsEvent.vue`) have been added to visually display the agent-generated conversation titles and
suggested follow-up questions to users.
### Changed
- 🔄 **Dynamic Thread Naming:** Conversation threads are now dynamically named by observing agent-generated
`ConversationTitleEvent`s, replacing generic "chat" names with topic-relevant titles derived from the conversation
content.
- ⚡️ **Enhanced Agent Workflows for Metadata:** Existing agent workflows have been updated to incorporate conversation
metadata generation, either through dedicated `step` decorators for non-terminal LLM answers or inline calls for
terminal stop events, ensuring robust, best-effort generation.
- 📡 **API and WebSocket Event Schema Updates:** The API and WebSocket event schemas have been extended to include the
new `ConversationTitleEvent` and `FollowUpQuestionsEvent` within the `DisplayEvents` discriminated union, ensuring
proper serialization and deserialization.
______________________________________________________________________
## [v0.301.7] - 2026-06-24 - Enhancements to Data Handling and Integration Consistency
### Fixed
- 🐛 **Improved Dataset Query Robustness:** Enhanced the dataset fetching logic to prevent errors by ensuring a dataset
ID is present before attempting to retrieve data, leading to a more stable user experience.
### Refactor
- 🧹 **Standardized Langfuse Configuration:** Aligned the Langfuse integration with the latest SDK conventions by
updating the host parameter name, improving internal consistency and maintainability.
______________________________________________________________________
## [v0.301.6] - 2026-06-23 - Improved OpenWebUI SCIM Client for Complete Data Sync
### Fixed
- 🐛 **Incomplete OpenWebUI SCIM Data Retrieval:** Resolved an issue where the OpenWebUI client would only retrieve the
first page of users or groups from the SCIM API, leading to incomplete data synchronization. The client now correctly
fetches all resources across multiple pages.
### Added
- ✨ **SCIM Pagination Mechanism:** Introduced a robust pagination logic (`_query_all`) for the OpenWebUI SCIM client,
ensuring all users and groups are retrieved, even when they span multiple pages.
- 🧪 **Comprehensive SCIM Pagination Tests:** Added new unit tests to thoroughly validate the SCIM pagination logic,
covering multi-page retrieval, server-capped page sizes, and scenarios where `totalResults` is omitted.
### Changed
- 🔄 **Updated SCIM Resource Listing:** Modified `list_users` and `list_groups` methods to leverage the new pagination
mechanism, ensuring they always return a complete list of all users and groups from OpenWebUI.
- 🔒 **Idempotent Group Creation Scan:** Enhanced the `create_group` method's check for existing groups to scan across
all SCIM pages, preventing duplicate group creation when the target group resides on a subsequent page.
______________________________________________________________________
## [v0.301.5] - 2026-06-22 - Agent Event API Refinements and Structural Updates
### Changed
- 🔄 **Event API Streamlining:** Refactored the core event system, consolidating various agent-related events such as
`UserMessageEvent`, `LLMStopEvent`, `StopEvent`, `RetrieverEvent`, and `BotInTheLoop` into a unified
`swiss_ai_hub.core.events.agent` module for improved organization and discoverability.
- ⚡️ **Enhanced Guard Event Clarity:** Renamed `ContextSufficientEvent` to **`ContextSufficientAcceptEvent`** and
`ContextInsufficientEvent` to **`ContextInsufficientRejectEvent`** to explicitly communicate their acceptance or
rejection semantics, making agent logic clearer.
- 📂 **Module Structure Alignment:** Relocated core components like `AgentConfig` and `KnowledgeRetriever` to more
logical paths within the `swiss_ai_hub.core` and `swiss_ai_hub.core.generative_ai` modules, respectively, enhancing
the overall package structure.
- 📄 **Updated Documentation Examples:** Modified the `README.md` example to reflect the latest event names and module
paths, ensuring up-to-date guidance for agent development.
______________________________________________________________________
## [v0.301.4] - 2026-06-22 - Enhanced Infrastructure Image Sourcing and Configuration
### Added
- ✨ **Introduced `Open WebUI Init` Service Configuration:** Added explicit image tag definition and usage for the
`Open WebUI Init` service, enabling central management of its PostgreSQL base image.
### Changed
- 🔄 **Standardized Third-Party Image Sourcing:** Implemented consistent registry prefixing for several critical
third-party Docker images, including **`OpenTelemetry Collector`**, **`rclone`**, **`Speaches`**, and
**`Docker Socket Proxy`**, to ensure all images are sourced from the designated internal registry.
- ⚡️ **Updated `OpenTelemetry Collector` Image:** Pinned the `OpenTelemetry Collector` image to a specific version
(`0.154.0`) for improved stability and security.
- 📄 **Refined `OpenWebUI Init` License Alias:** Updated the internal license alias mapping for the `OpenWebUI Init`
service to accurately reflect its `postgres` base image.
### Refactor
- 🧹 **Standardized `Keycloak Config CLI` Variable Name:** Renamed the internal configuration variable for
`Keycloak Config CLI` to `keycloak_config_cli` (snake_case) for improved consistency across the deployment
configuration.
______________________________________________________________________
## [v0.301.3] - 2026-06-22 - Keycloak Superuser Configuration Alignment
### Changed
- ⚙️ **Keycloak Superuser Configuration:** Standardized the superuser's username in Keycloak realms and bootstrap
configurations to consistently use the `SUPERUSER_EMAIL` environment variable instead of `SUPERUSER_USERNAME`.
______________________________________________________________________
## [v0.301.2] - 2026-06-22 - Default Chat TTS Permission Adjustment
### Changed
- 🎚️ **Updated Default Chat TTS Permission:** The default setting for Text-to-Speech (TTS) functionality in chat has
been changed from enabled (`True`) to disabled (`False`) across all Docker Compose configurations. Users can still
explicitly enable this permission if desired.
______________________________________________________________________
## [v0.301.1] - 2026-06-19 - Enhanced Router Stability and Dependency Management
### Fixed
- 🐛 **Ensured `vue-router` Consistency:** Addressed critical runtime errors, such as
`TypeError: Invalid value used as weak map key` and `useRouter()` returning `undefined`, which occurred due to
`@vueuse/router` resolving to a different major version of `vue-router` than the Nuxt application. This is resolved by
explicitly declaring `vue-router@4.6.4` as a direct dependency, guaranteeing consistent injection keys.
- 📄 **Updated `vue-router` Pinning Guidance:** Significantly updated the `README.md` with crucial instructions on how to
reliably pin `vue-router` as a direct dependency. This new guidance clarifies why previous override methods were
insufficient and provides a robust solution to prevent dependency resolution conflicts and ensure application
stability.
______________________________________________________________________
## [v0.301.0] - 2026-06-19 - Enhanced OpenWebUI Agent Display Names with Locale Support
### Added
- ✨ **New Environment Variable `OPENWEBUI_MODEL_NAME_LOCALE`**: Introduced to allow configuring the preferred locale for
displaying agent names within OpenWebUI, providing a more localized user experience.
- 🌍 **Locale-Aware Agent Display Names**: Implemented the ability for OpenWebUI to resolve and display agent names based
on the configured locale, with intelligent fallbacks to other available translations or the agent ID.
- 🔄 **OpenWebUI Workspace Model Update Capability**: Added functionality to the OpenWebUI provisioner to update existing
workspace models, specifically enabling seamless name changes in response to agent renames or locale adjustments.
### Changed
- 🚀 **OpenWebUI Agent Discovery Language Negotiation**: The OpenWebUI `aihub_pipeline` now includes an `Accept-Language`
header during agent discovery API calls, ensuring that agent details are fetched in the configured locale.
- 📡 **Robust Agent Name Synchronization**: The agent synchronization mechanism has been enhanced to detect and propagate
changes to agent names, ensuring that OpenWebUI workspace models accurately reflect the latest, locale-resolved agent
names.
- 🛠️ **Agent Hash Calculation**: The algorithm for computing agent hashes now incorporates the agent's display name,
making synchronization more sensitive to name-only changes and preventing stale entries in OpenWebUI.
### Refactor
- 🧹 **Streamlined OpenWebUI Model Reconciliation**: The logic for determining changes in OpenWebUI workspace models has
been refined to explicitly distinguish between models that need to be created, updated, or deleted, improving
provisioning efficiency and clarity.
______________________________________________________________________
## [v0.300.4] - 2026-06-18 - Improved Data Lake Partition Naming for Enhanced Isolation
### Fixed
- 🐛 **Resolved `DynamicPartitionsDefinition` Name Collisions**: Updated the `default_rclone_to_datalake_definitions`
utility to dynamically generate unique names for `DynamicPartitionsDefinition` instances. This prevents unintended
sharing of partition state when multiple rclone-backed sources are registered in the same Dagster instance, ensuring
proper data isolation.
### Added
- 📄 **Documented Partition Naming Conventions**: Added clear guidelines for `DynamicPartitionsDefinition` naming,
emphasizing the importance of deriving unique names from the data lake container and source to prevent state
collisions.
- 🧪 **Introduced Partition Naming Regression Test**: Added a new test case to verify that
`default_rclone_to_datalake_definitions` correctly produces distinct partition definition names, safeguarding against
future naming conflicts.
______________________________________________________________________
## [v0.300.3] - 2026-06-18 - Package Version Synchronization
### Changed
- 🔄 **Synchronized Package Versions:** All core Python packages (`agent`, `api`, `backup`, `bot`, `core`, `meta`,
`pipeline`, `process`, `sysadmin-api`) and JavaScript packages (`sysadmin-web`, `web`) have been updated to
`v0.300.3`. This ensures consistent versioning across the entire Swiss AI Hub platform.
______________________________________________________________________
## [v0.300.2] - 2026-06-18 - Homepage Navigation Refinement
### Changed
- 📄 **Updated Platform Overview Navigation:** Streamlined the "Platform Overview" link on the homepage for both English
and German versions, directing users to the broader architecture section for an improved overview of platform
documentation.
______________________________________________________________________
## [v0.300.1] - 2026-06-17 - Enhanced User Interface Accessibility
### Changed
- ♿️ **Improved UI Accessibility:** Added `aria-label` attributes to numerous interactive components across the
application, including various `Select` dropdowns, `Textarea` inputs, `InputText` fields, and file upload inputs. This
enhancement significantly improves the experience for users relying on screen readers and other assistive technologies
by providing clearer context for UI elements.
- ⚙️ **Refined Form Element Identification:** Updated and added `id` attributes to select form components, ensuring
better programmatic identification and improved accessibility.
- 🌐 **Expanded Internationalization:** Introduced new placeholder translations for chat inputs
(`thread.chat.placeholder`) across multiple languages (German, English, French, Italian) to support enhanced UI
descriptions.
______________________________________________________________________
## [v0.300.0] - 2026-06-17 - Streamlined Agent Instance Management with Auto-Granted Creator Access
### Added
- 🚀 **Automated Creator Access for Agent Instances**: Tenant administrators can now create agent instances and
immediately gain full admin access (open, edit, delete, chat) without requiring manual intervention from a sysadmin.
This significantly improves the self-service capabilities for agent instance management.
- 📄 **Architectural Decision Record (ADR)**: A new ADR has been added, documenting the decision for automatically
granting per-instance access to creators and detailing the context, drivers, decision, and consequences.
- 🔑 **New Access Control Helpers**: Introduced dedicated `AccessChecker` methods (`agent_instance_admin_rule`,
`agent_instance_user_rule`, `rules_grant_admin_to_agent_instance`) to precisely manage and evaluate instance-level
permissions.
- 🦾 **Persistence Utilities for Access Management**: Added robust utility methods including
`TenantMetadataEntity.grant_access_rule`, `TenantMetadataEntity.revoke_access_rule_from_all_tenants`, and
`RoleEntity.delete_role_from_all_tenants` for managing tenant access ceilings and dedicated per-instance roles.
- ✅ **Comprehensive Test Coverage**: Included extensive unit and integration tests to ensure the reliability and
correctness of the new auto-grant and cleanup mechanisms for agent instance access.
### Changed
- 🔄 **Updated Agent Instance Lifecycle in API**: The `AgentService` now orchestrates the automatic granting of
per-instance admin access during agent instance creation and performs thorough cleanup of associated access rules and
roles upon deletion, including a rollback mechanism for failed grants.
- ⚡️ **Improved Frontend Cache Synchronization**: Frontend components now automatically invalidate relevant caches
(agent instances, tenant roles, user profiles) after creating or deleting agent instances, ensuring immediate
reflection of access changes in the UI.
- 🖼️ **Enhanced Agent List Display Logic**: Refined the display of agent groups and empty states on the agents page,
improving user experience when applying filters or viewing available agent classes.
### Refactor
- 🧹 **Standardized Agent Rule Generation**: The `AccessChecker` now uses dedicated helper methods to generate canonical
user and admin rules for agent instances, improving consistency and maintainability of access control logic.
______________________________________________________________________
## [v0.299.0] - 2026-06-17 - Streamlined Keycloak Management and Expanded LLM Support
### Added
- ✨ New LLM models, **Fable** and **Fable 1M**, are now available for use.
- 🚀 A new `keycloak-config` service has been introduced to enable declarative, idempotent management of Keycloak realm
configurations, ensuring consistency across deployments.
- 📄 A new Architecture Decision Record (ADR) detailing the **Declarative Keycloak Realm Reconciliation** outlines the
strategy for the updated configuration management system.
- 📦 Keycloak configuration templates are now organized into `bootstrap/` (first-start only) and `managed/` (reconciled
on every start) directories for clearer lifecycle management.
### Changed
- 🔄 The **Keycloak configuration management** workflow has been fundamentally revised, transitioning from mixed
first-start imports and imperative `kcadm` scripts to a fully declarative approach using the new `keycloak-config`
service.
- 📄 **Keycloak documentation** has been extensively updated to clearly define the new two-phase configuration lifecycle
(bootstrap vs. managed) and its implications for operators, including how changes are applied and maintained.
- 🔑 **Azure Entra ID setup instructions** for platform roles have been clarified, specifying that `AIHubAccess` and
`AIHubSysAdmin` are the only Keycloak realm roles to be mapped from the Identity Provider. Other platform roles are
now explicitly stated to be managed internally by the platform.
- 🔐 Access to **SeaweedFS Filer** and other admin tool UIs now consistently requires the `AIHubSysAdmin` realm role,
enhancing security and aligning access control across administrative interfaces.
- 📚 The **Environment Variables documentation** has been thoroughly revised to reflect the new Keycloak configuration
structure and the services consuming each variable.
- 📝 German documentation for **Platform vs. SDK** and the **Ecosystem Model** has been updated with minor wording
improvements and corrected license references to `AGPL-3.0-or-later`.
### Removed
- 🗑️ The monolithic `keycloak-realm.json.j2` and `keycloak-identity-providers.json.j2` templates have been retired,
replaced by the new modular `bootstrap/` and `managed/` Keycloak configuration files.
- 🗑️ Imperative `kcadm` commands for identity provider and Langfuse sysadmin gate reconciliation have been removed from
the `keycloak-entrypoint.sh` script, as these are now handled declaratively by the `keycloak-config` service.
### Refactor
- 🧹 The **Keycloak entrypoint script (`keycloak-entrypoint.sh`)** has been significantly simplified by delegating
managed configuration reconciliation to the `keycloak-config` service.
- 🛠️ The internal `generate_compose.py` tooling has been refactored to support the new modular Keycloak configuration
structure, including logic to merge bootstrap and managed documents for the initial realm import.
- ⚙️ Keycloak's realm definition has been restructured across multiple, smaller JSON files within `bootstrap/` and
`managed/` to improve modularity and maintainability.
______________________________________________________________________
## [v0.298.3] - 2026-06-16 - Configuration Forms: Robustness and User Experience Improvements
### Fixed
- 🐛 **Configuration Form Defaults:** Resolved an issue where nullable fields with default values were not correctly
initialized on new form creation, leading to silent data loss of defaults. Forms now consistently apply declared
default values, ensuring a predictable user experience.
- 🐛 **Repeater Row Stability:** Eliminated data corruption and UI inconsistencies in repeater components by implementing
stable, unique keys for each row. This prevents issues caused by re-indexing when non-last rows are removed from a
list.
- 🐛 **Fractional Input Fields:** Corrected the behavior of numeric input fields (`InputNumber`) to properly accept and
display decimal values for fractional properties (e.g., LLM `temperature`), which previously rejected decimal points.
- 🐛 **Form Rendering Robustness:** Enhanced the dynamic form builder to handle malformed schema elements gracefully. A
single invalid field will now be skipped and logged without causing the entire form section to disappear or become
unusable.
- 🐛 **UI Performance & Stability:** Addressed intermittent blank or broken form displays by refactoring label and option
resolvers to use synchronous case conversion, eliminating reactive effect leakage and improving rendering stability.
### Changed
- 🔄 **Unified Form Data Handling:** The agent and process configuration forms now utilize a single, consistent pipeline
for hydrating initial data and serializing submissions. This ensures identical behavior and data integrity across both
creation and editing workflows.
- 📄 **Nullable Field Toggle Logic:** Extended the backend form schema to include a `default_enabled` flag for nullable
elements. This accurately indicates whether a field with a non-null default value should appear enabled by default on
a fresh form.
- ⚡️ **Symmetric Backend Normalization:** Agent and process creation endpoints now apply the same server-side
configuration normalization as update operations. This prevents internal UI artifacts (like FormKit `__validate__`
keys) from persisting in the configuration data.
### Refactor
- 🧹 **Case Conversion Utility:** Replaced the `useChangeCase` composable with a direct, synchronous `capitalCase`
utility across various UI components, contributing to the overall stability and performance of form rendering.
______________________________________________________________________
## [v0.298.2] - 2026-06-16 - Documentation Site Migration to Custom Domain
### Added
- 📄 **Custom Domain Configuration:** Integrated support for a custom domain (`docs.ai-hub.bbv.ch`) for the documentation
site, enhancing branding and direct access.
- 🌐 **Sitemap Generation:** Enabled sitemap generation for the documentation portal, improving search engine
optimization and content discoverability.
- 🤖 **Crawler Access:** Updated the `robots.txt` file to allow web crawlers to access and index the entire documentation
site.
### Changed
- 🚀 **Documentation Base Path:** Modified the documentation site's base path to be served from the root `/` (instead of
`/aihub-core/`), aligning with the new custom domain setup.
- 🔗 **Live URL and Deployment Details:** Revised internal configuration and documentation to reflect the new live URL
(`https://docs.ai-hub.bbv.ch/`) and updated deployment specifics.
______________________________________________________________________
## [v0.298.1] - 2026-06-16 - Enhanced Agent List Display and Filtering
### Changed
- 🖼️ **Refined Agent Group Display:** The Agents page now intelligently shows or hides agent group headers based on
active filters. When filters are applied, only groups with instances matching the criteria will display a header,
providing a cleaner and more relevant overview. When no filters are active, all available agent classes will show
their headers.
______________________________________________________________________
## [v0.298.0] - 2026-06-16 - Introducing Document Deletion and Enhanced Management for Knowledge Bases
### Added
- ✨ **Document Deletion API Endpoints:** Introduced new API endpoints for deleting individual documents and performing
batch deletions from knowledge bases.
- 🗑️ **Core S3 File Deletion:** Added foundational S3 service capabilities to permanently remove source files from data
lakes as part of the document deletion process.
- 🚀 **Asynchronous Document Cleanup:** Implemented service logic to schedule the cleanup of document and vector stores
via pipeline reconciliation after a document's source file is removed.
- 📄 **Batch Deletion Data Transfer Objects:** Introduced new DTOs for requests and responses to support the new batch
document deletion functionality, providing clear status for each document.
- 🖼️ **Interactive Document Deletion UI:** Added user interface elements to the document list for both single and
multi-select document deletion, including confirmation dialogs and progress indicators.
- ⚡️ **Real-time Deletion Status Tracking:** Developed front-end composables to manage the state of scheduled document
deletions, providing visual feedback to users even after page refreshes.
- 🌐 **Localized Deletion Messages:** Integrated comprehensive localization for all new document deletion features across
multiple languages.
- 🧪 **Extensive Deletion Service Tests:** Added a new suite of unit tests to ensure the reliability and correctness of
the knowledge document deletion service.
### Fixed
- 🐛 **Robust Metadata Table Generation:** Corrected an issue in the pipeline's metadata utility to ensure consistent and
valid table structures when displaying heterogeneous document metadata, preventing errors in Dagster reports.
### Changed
- 🔄 **Updated Document List Experience:** Enhanced the document list to clearly indicate documents pending deletion and
integrated hooks for refreshing data after deletion events.
- ✍️ **Clarified Source Update Event Purpose:** Updated the documentation for the `SourceUpdatedEvent` to explicitly
state its role in triggering both ingestion and cleanup processes within the pipeline.
### Refactor
- 🧹 **Standardized S3 URI Handling:** Centralized the definition and usage of the S3 URI scheme within the knowledge
service for improved consistency and maintainability.
______________________________________________________________________
## [v0.297.9] - 2026-06-16 - Streamlined Agent Evaluations with Enhanced Langfuse Integration
### Changed
- 🔄 **Updated Agent Evaluation Workflow:** The agent evaluation process has been re-architected to deeply integrate with
**Langfuse**, establishing it as the primary platform for conducting experiments and managing evaluators. The Swiss AI
Hub now auto-provisions all necessary components for a seamless Langfuse experience.
- 📄 **Revised Evaluation Documentation:** The comprehensive documentation for agent evaluations has been completely
updated to guide users through the new Langfuse-centric workflow, detailing how to create datasets, configure
evaluators, and execute experiments effectively.
- ⚡️ **Simplified Evaluator Configuration:** The documentation for creating evaluators has been streamlined, emphasizing
recommended scoring dimensions and highlighting the auto-provisioned LLM connection for judge models directly within
Langfuse.
### Removed
- 🗑️ **Deprecated Experiment UI Images:** Outdated screenshots depicting the previous in-platform UI for running
experiments have been removed to align with the new Langfuse-based evaluation workflow.
______________________________________________________________________
## [v0.297.8] - 2026-06-16 - Refined Localization and Translation Capabilities
### Changed
- ✨ **Enhanced Locale String Handling:** The `LocaleString` utility has been significantly improved with a more robust
`in_locale` method, now offering intelligent fallback logic to ensure the best available translation is always
retrieved. This includes prioritizing the exact requested locale, falling back to a default, and then to other
available languages.
- 🔄 **Precision in Translation Service:** The `TranslationService` now explicitly requests translations for the exact
source locale without automatic fallbacks, ensuring greater control and precision in the translation process,
leveraging the new `LocaleString` enhancements.
### Added
- ✅ **Expanded Unit Tests for Localization:** New, comprehensive unit tests have been introduced for the `LocaleString`
class, rigorously validating the new fallback logic and precise locale retrieval mechanisms to ensure the reliability
of internationalization features.
______________________________________________________________________
## [v0.297.7] - 2026-06-16 - Dashboard UI and Chart Display Improvements
### Fixed
- 🖼️ **Dashboard Grid Filters:** Resolved potential display and layering issues for dropdown menus within the
**Dashboard Grid** by ensuring proper rendering of event data type and agent selection options.
- 📈 **Event Timeseries Chart:** Enhanced the visual layout and readability of the **Event Timeseries Chart** by
fine-tuning chart and data label vertical positioning, including responsive adjustments for smaller screens.
______________________________________________________________________
## [v0.297.6] - 2026-06-16 - Strengthened OpenWebUI Group Management and Sync Reliability
### Added
- 📄 **Improved logging:** Added logging capabilities to the OpenWebUI client for better operational visibility during
interactions with the OpenWebUI API.
### Fixed
- 🐛 **Prevented duplicate OpenWebUI groups:** The `create_group` operation is now idempotent. It intelligently reuses an
existing OpenWebUI group if one with the same display name is found, preventing the creation of duplicate groups that
could disrupt role-to-group synchronization.
- 🔐 **Enhanced group synchronization reliability:** Implemented a dedicated, blocking Redis lock for OpenWebUI group
reconciliation. This prevents concurrent synchronization processes from creating duplicate groups or causing
inconsistent states, ensuring robust group management.
- ⚡️ **Increased group sync lock timeout:** The maximum duration for the group synchronization lock has been
significantly extended from 60 to 600 seconds. This prevents premature lock expiration for potentially long-running
group reconciliation operations on large tenants.
### Changed
- 🔄 **Improved Redis lock robustness:** The Redis lock acquisition and release mechanism has been enhanced to gracefully
handle scenarios where a lock auto-expires before it can be explicitly released. This prevents failures and provides
warnings for better operational insights.
### Refactor
- 🧹 **Refactored group synchronization logic:** The core group reconciliation logic within the provisioner has been
extracted into a new method (`_sync_groups_locked`), improving code clarity and better isolating the critical section
protected by the dedicated synchronization lock.
______________________________________________________________________
## [v0.297.5] - 2026-06-15 - Enhanced Error Reporting for Clearer Notifications
### Fixed
- 🐛 **Improved Error Message Display:** Enhanced the client-side error handling to correctly parse and display detailed
error messages received as arrays, ensuring users receive comprehensive and readable notifications in toast alerts.
______________________________________________________________________
## [v0.297.4] - 2026-06-15 - Enhanced Empty State Feedback
### Added
- ✨ **Improved Empty State Feedback:** Introduced clear "no results" messages for the Processes, Memory Graph, and
Memory List pages, ensuring users receive visual feedback when no items are available.
- 📄 **New Localization Strings:** Added new translation keys for "no results" messages across multiple languages
(German, English, French, Italian) to support the improved empty state feedback.
______________________________________________________________________
## [v0.297.3] - 2026-06-15 - Streamlined Sysadmin Tenant Routing
### Changed
- 🔄 **Simplified Sysadmin Tenant Paths:** The `/sysadmin` URL prefix has been removed from all tenant administration
routes. Sysadmin functionality is now directly accessible under `/tenants`, making the URL structure more concise and
aligned with the sysadmin application's primary function.
- 🧹 **Refined `useTenantPath` Logic:** The `useTenantPath` composable now explicitly focuses on generating paths for
tenant-scoped `/[tenant]/...` routes. Sysadmin routes (`/tenants/...`) are now treated as absolute and no longer
require dynamic tenant injection through this composable, clarifying and streamlining path generation logic.
- 📄 **Updated Sysadmin Documentation:** The project documentation has been updated to reflect the new, simplified
sysadmin routing structure, providing clearer guidance on tenant administration paths.
______________________________________________________________________
## [v0.297.2] - 2026-06-15 - Improved LiteLLM User & Key Management
### Fixed
- 🐛 **Enhanced LiteLLM provisioning resilience:** Improved the user and API key generation process to gracefully handle
concurrent requests and race conditions by treating `409 Conflict` responses for user or key creation as successful,
pre-existing entities. This makes the service more robust in high-concurrency environments.
### Refactor
- 🧹 **Streamlined LiteLLM key provisioning:** The internal logic for managing LiteLLM users and API keys has been
refactored into distinct, more focused methods (`_create_user_if_absent`, `_generate_key`) to improve clarity,
maintainability, and error handling.
### Added
- 🧪 **Comprehensive LiteLLM service unit tests:** Introduced a new suite of unit tests for the `LiteLLMService` covering
various scenarios, including existing users, new user creation, and handling of concurrency conflicts during user and
key provisioning.
______________________________________________________________________
## [v0.297.1] - 2026-06-15 - Stricter Langfuse Access Control and Local Dev Setup Enhancements
### Security
- 🔑 **Restricted Langfuse UI Access:** Implemented Keycloak-level access control for the Langfuse UI, ensuring that only
users with the `AIHubSysAdmin` realm role can log in. This prevents unauthorized users from viewing sensitive trace
data by denying access directly at the Keycloak login flow.
- 🔑 **Idempotent Keycloak Flow Reconciliation:** Added robust scripting to the Keycloak entrypoint that idempotently
applies authentication flow configurations, including the new Langfuse access gate, on every container start. This
ensures security policies are consistently enforced across all deployments, especially existing ones.
- 📄 **Documented Langfuse Sysadmin Gate:** Introduced new architecture decision records (ADRs) and comprehensive
deep-dive documentation explaining the detailed mechanism, rationale, and structural rules for the Langfuse sysadmin
gate within Keycloak.
- 📄 **Updated Observability and Authentication Documentation:** Enhanced platform documentation in both English and
German to clearly reflect the new Langfuse access restrictions and the role of the `AIHubSysAdmin` in controlling
access to administrative tools.
### Fixed
- 🐛 **Langfuse OIDC Callback in Local Environments:** Resolved an issue where Langfuse's OIDC backchannel to Keycloak
would fail in local and build environments when using self-signed `mkcert` certificates. The Langfuse container now
correctly trusts the `mkcert` root CA, allowing successful OIDC authentication.
### Added
- 🛠️ **Automated `mkcert` Root CA Copy:** The `make local-cert` command now automatically copies the `mkcert` root CA
certificate into the Traefik certificates directory, streamlining local development setup for secure communication.
- 📄 **Keycloak Configuration Deep-Dive:** Added new, comprehensive documentation sections providing an overview of the
`aihub` Keycloak realm, including its clients, scopes, roles, tenant groups, identity brokering, and the mechanisms by
which configuration changes are applied to running instances.
### Changed
- ⚙️ **Keycloak Realm Configuration Update:** Modified the Keycloak realm configuration across all deployment stages to
bind a custom browser flow and introduce a new marker client scope (`langfuse-sysadmin-gate`), enabling the
conditional access denial logic for Langfuse.
- 🧹 **Documentation Sync Exclusions:** Updated the documentation synchronization script and `.gitignore` to specifically
preserve hand-authored Keycloak configuration deep-dive documentation sections during automated content updates.
### Refactor
- 📄 **Minor Documentation Adjustments:** Performed various minor formatting and wording adjustments across several
existing documentation files to improve clarity and readability.
______________________________________________________________________
## [v0.297.0] - 2026-06-15 - Self-Aware Agents: Sm🧠rter Conversations and Robust Workflows
### Added
- 🦾 **Agent Self-Awareness Feature:** Introduced the ability for conversational agents to detect and answer
meta-questions about themselves (e.g., "What can you do?", "Who are you?") based on their identity, capabilities, and
behavior, instead of running their normal pipeline.
- ✨ **New Self-Awareness Events:** Created `MetaQuestionDetectedEvent` to signal when a user's query is a meta-question
about the agent, and `NotAMetaQuestionEvent` to act as an "all-clear" gate, releasing the agent's normal workflow.
- 💬 **Localized Meta-Question Handling:** Added comprehensive i18n support for self-awareness steps, detection prompts,
and answer prompts, enabling agents to respond to meta-questions in multiple languages.
- 📄 **Workflow Mermaid Flowchart Generation:** Implemented a new utility to generate Mermaid flowcharts of an agent's
workflow (excluding self-awareness steps) to help ground the agent's answers to meta-questions about its processes.
- 🧪 **Comprehensive Self-Awareness Testing:** Introduced a suite of unit and integration tests, including a compliance
test, to ensure self-awareness features are correctly wired into conversational agents and prevent common pitfalls
like race conditions.
- 🖼️ **UI Support for Meta-Question Events:** Added a new Vue component to display `MetaQuestionDetectedEvent` in the
UI, providing visual feedback when an agent is handling a meta-question.
### Changed
- 🔄 **Conversational Agents Integrated with Self-Awareness:** The `RAGAgent`, `ExpertRAGAgent`, `FewShotAgent`,
`LLMWrappingAgent`, `McpReactAgent`, and `NamespaceSelectionAgent` blueprints now explicitly define steps for
detecting and answering meta-questions, and their entry points are gated to prevent conflicts.
- 📝 **Updated Agent Documentation and Guidance:** Refreshed the `SKILL.md` and `CLAUDE.md` documents with detailed
guidelines on integrating and testing self-awareness in new and existing conversational agents.
### Fixed
- 🐛 **Robust Chat History Limiting:** Enhanced the chat history limiting mechanism to proactively filter out empty chat
messages, preventing potential 400 errors from LLM providers and improving overall agent reliability.
### Refactor
- 🧹 **Decoupled Self-Awareness Implementation:** Architecturally decided to implement self-awareness as explicit,
per-agent steps rather than a shared base-class mixin. This keeps agent workflows transparent and avoids invasive
changes to core `Agent` machinery, ensuring cleaner integration and easier debugging.
______________________________________________________________________
## [v0.296.5] - 2026-06-15 - Improved RAG Agent Context Persistence for Follow-up Questions
### Fixed
- 🐛 **RAG Agent follow-up context loss**: Addressed a critical bug where the RAG agent would fail to answer affirmative
replies to offered follow-up questions due to the loss of essential grounding context between conversation turns.
### Added
- ✨ **Grounding node persistence**: Introduced a new mechanism to automatically persist and carry over the most relevant
grounding documents from a previous turn into the current conversation turn, ensuring continuous context for follow-up
questions.
- 📄 **`grounding_nodes` to `InOrderNodeCombinerEvent`**: Added a new field to the `InOrderNodeCombinerEvent` to
explicitly track and expose the complete set of nodes (comprising both fresh retrieval and carried prior-turn nodes)
that collectively ground the agent's answer.
- 🚀 **Dedicated RAG agent steps**: Implemented new workflow steps (`persist_grounding_nodes_step`,
`do_read_carried_grounding_nodes`, `do_persist_grounding_nodes`) within the RAG agent to manage the lifecycle and
integration of carried grounding nodes.
- 🧪 **Regression test for follow-up affirmation**: Introduced a comprehensive regression test to validate the fix and
prevent future regressions related to context retention in multi-turn follow-up scenarios.
### Changed
- 🔄 **Enhanced context combination logic**: Modified the `order_nodes_by_documents_step` to seamlessly integrate carried
grounding nodes alongside newly retrieved content, creating a more robust and complete context for generating
responses to follow-up questions.
______________________________________________________________________
## [v0.296.4] - 2026-06-15 - Enhanced LLM Parameter Precision
### Changed
- 🎨 Improved the display precision of the **LLM temperature parameter** slider in the UI, ensuring it shows a maximum of
one decimal place for better readability and user experience.
______________________________________________________________________
## [v0.296.3] - 2026-06-15 - Enhanced Prompt Configuration for Agents and Steps
### Changed
- 🔗 **Mandatory System Prompts:** The `system_prompt` field for both the **`LLMWrappingAgent`** and **`FewShotStep`** is
now mandatory, ensuring that these components always have a defined behavioral context.
- 📄 **LLMWrappingAgent Prompt Clarity:** The description for the **`LLMWrappingAgent`'s system prompt** has been updated
to more accurately reflect its purpose in setting the agent's behavior for the wrapped LLM.
______________________________________________________________________
## [v0.296.2] - 2026-06-12 - Expanded Security Configuration for Backup Services
### Added
- 🔑 **Added Keycloak OAuth2 Proxy Secret for Backup Service:** Introduced support for a new
`KEYCLOAK_OAUTH2_PROXY_BACKUP_SECRET` environment variable across all Docker Compose configurations, enabling secure
access to a dedicated backup service via Keycloak OAuth2 Proxy.
______________________________________________________________________
## [v0.296.1] - 2026-06-11 - Deployment: Enhanced Traefik Network Integration
### Changed
- 🌐 **Enhanced Traefik network configuration:** Explicitly assigned the Langfuse service to the `proxy` network for
Traefik, improving routing and integration in self-hosted Docker deployments.
______________________________________________________________________
## [v0.296.0] - 2026-06-11 - Empowering Data Discovery: Advanced Filtering, Search, and Sorting for Agents and Threads
### Added
- ✨ **Agent Instance Filtering and Search:** Introduced new API capabilities and corresponding UI elements allowing
users to filter agent instances by **class** and search by **name**, significantly improving agent discovery.
- 🚀 **Advanced Thread Management:** Implemented comprehensive filtering options for threads, including search by
**name**, filtering by associated **agent ID**, specific **user ID**, run **status** (active, completed, failed), and
a flexible **date range**.
- 📈 **Thread Sorting Capabilities:** Added the ability to sort threads by **name** or **creation date** in both
ascending and descending order, providing more control over how threads are organized and viewed.
- 🦾 **Thread Status Classification:** Introduced robust backend logic to accurately classify threads into `active`,
`completed`, or `failed` states based on their agent event history, enabling precise status filtering.
### Changed
- 🔄 **API Query Parameter Expansion:** The `getAllAgentInstances` and `getPaginatedThreadsForUser` API endpoints have
been extended to support the new filtering, searching, and sorting parameters, providing more powerful data retrieval.
- 🌐 **UI Enhancements for Agents and Threads:** The web interface for both Agent Instances and Threads has been
significantly updated to expose and utilize all new filtering, search, and sorting functionalities, improving user
experience and data navigation.
### Refactor
- 🧹 **Optimized Agent Configuration Search:** Refactored the agent configuration search logic to support multilingual,
case-insensitive substring matching for agent names, enhancing search flexibility.
- ⚙️ **Streamlined Thread Query Logic:** Introduced dedicated helper methods (`_apply_filters`, `get_order_by`) within
the `ThreadEntity` for more modular, efficient, and maintainable query construction.
______________________________________________________________________
## [v0.295.4] - 2026-06-11 - Improved Authentication Flow and Persistent User Sessions
### Added
- ✨ **Architectural Decision Record for Keycloak Sessions:** Documented the rationale, drivers, and implementation
details for extending Keycloak Single Sign-On (SSO) session lifespans, outlining the benefits for user experience and
system behavior.
- ⚙️ **Centralized Keycloak Session Configuration:** Introduced new configuration parameters (`sso_session_idle_timeout`
and `sso_session_max_lifespan`) in `compose-config.yml`, providing a single source of truth for Keycloak realm session
settings.
### Changed
- 🚀 **Extended Keycloak SSO Session Lifespans:** Increased Keycloak SSO session idle timeout to 5 days and maximum
lifespan to 30 days. This significantly reduces the frequency of forced re-logins, allowing users to stay logged in
across nights and weekends for a smoother experience.
- 🔄 **Automatic Keycloak Session Lifespan Migration:** Implemented logic within the Keycloak entrypoint script to
automatically update SSO session lifespans for existing deployments. This ensures that environments originally
configured with Keycloak's default short lifespans are automatically upgraded to the new, longer durations without
overwriting manual operator customizations.
- ⚡️ **Optimized Home Page Redirection:** Enhanced the login experience by implementing smart redirection. Users are now
automatically directed to their last active tenant after successful login or silent token renewal, leveraging
persisted active tenant information.
______________________________________________________________________
## [v0.295.3] - 2026-06-11 - Improved Streaming State and Richer Responses
### Refactor
- 🧹 **Enhanced Streaming State Management Initialization:** The `StreamingStateManager` is now initialized earlier in
the processing pipeline, ensuring comprehensive state capture from the very beginning of a request.
### Changed
- ⚡️ **Richer Output for Successful Operations:** The pipeline now consistently returns serialized HTML content from the
streaming state manager upon successful completion, providing more detailed and structured results to the user
interface.
- 📄 **Contextual Error Reporting:** Error messages now include any available partial streaming state (serialized as
HTML) alongside the error details. This provides users with more context and partial results, making it easier to
understand and debug issues.
______________________________________________________________________
## [v0.295.2] - 2026-06-11 - Improved Tenant Management and Documentation Clarity
### Added
- 🖼️ **New Visual Aids for Tenant Creation:** Integrated several new screenshots to provide clear, step-by-step guidance
for Keycloak group creation and navigating the Tenant Administration UI.
- 🔑 **Dedicated `AIHubSysAdmin` Role:** Introduced the `AIHubSysAdmin` Keycloak realm role, which is now required for
creating and configuring tenants, enhancing security and control over multi-tenancy lifecycle management.
### Changed
- 📄 **Revamped Tenant Creation Documentation:** The "Creating Tenants" guide has been significantly updated with a
detailed, step-by-step process, clarifying the interplay between Keycloak groups and platform metadata, tenant states,
and in-tenant role assignments.
- 📚 **Enhanced German Architecture Documentation:** Made minor textual improvements and clarifications across various
German architecture documents, including system context, containers, and package-centric views, for improved
readability and accuracy.
______________________________________________________________________
## [v0.295.1] - 2026-06-11 - Enhanced AI-Driven GitHub Issue Management
### Added
- ✨ **New AI Skill: `write-issue`**: Introduced a powerful new AI skill that enables automated authoring, creation, and
management of GitHub issues within the `bbvch-ai/aihub-core` repository. This skill ensures adherence to established
project conventions for titles, body structure, labels, and project board integration.
- 📄 **Comprehensive Issue Creation Guidelines**: Detailed a comprehensive set of conventions and steps for drafting
GitHub issues, covering everything from title formatting (Epic/Story vs. Task/Bug), structured body content
(`In scope`, `Out of scope`, `Accepted when`), required `area:*` and optional `version` labels, to setting project
board fields like Item Type, Priority, and Status.
- ⚙️ **Automated Issue Validation Script**: Implemented a new Bash script (`validate-issue.sh`) to automatically verify
that newly created GitHub issues conform to project standards. This script checks for correct labeling, the presence
of specific body sections, checkbox acceptance criteria, and proper placement on the AI-Scrum board with an assigned
Item Type.
______________________________________________________________________
## [v0.295.0] - 2026-06-11 - Streamlined Bot Setup and Clearer User Provisioning
### Added
- ✨ **New Architecture Decision Record (ADR)**: Documented the decision to prevent bot-first Keycloak provisioning,
requiring users to log in to the web portal once before interacting with the bot. This clarifies the user onboarding
process and ensures consistent identity management.
- 🦾 **`UserNotProvisionedError`**: Introduced a specific exception for users attempting to interact with the bot before
their Keycloak account has been provisioned via web login, allowing for targeted error handling.
- 📄 **Localized Bot Messages**: Added new internationalized messages across German, English, French, and Italian,
providing clear and actionable guidance to users who are not yet provisioned in Keycloak.
- ⚙️ **`BOT_DEV_FAKE_EMAIL` Environment Variable**: Introduced a new environment variable for local development to
simulate user identity for the bot, bypassing the Teams connector and enabling comprehensive testing of authentication
flows without Azure.
- ⚡️ **`primary_frontend_origin` Setting**: Added a convenience property to `AIHubSettings` to easily retrieve the
primary web portal URL, used for directing unprovisioned users to the login page.
### Changed
- 🚀 **Overhauled Bot Local Development Setup**: Significantly revised the local development documentation for the bot,
providing detailed, step-by-step instructions for using the Bot Framework Emulator, including support for WSL2 and
anonymous authentication.
- 🔄 **Bot User Identity Resolution**: Modified the bot's identity resolution logic to raise a specific
`UserNotProvisionedError` when a user's email is not found in Keycloak, replacing a generic `ValueError`.
- 💬 **Actionable Error Handling for Unprovisioned Users**: The bot now gracefully handles `UserNotProvisionedError` by
replying with a clear, localized message instructing users to perform a one-time web portal login, rather than
displaying an opaque error.
- 🌎 **WSL2 Compatibility for Local Bot Runner**: Updated the local bot runner (`main.py`) to bind to `0.0.0.0:8001`,
enabling seamless connectivity from the Bot Framework Emulator running on Windows when the bot is hosted in WSL2.
- 🔑 **Anonymous Authentication for Local Bot Runner**: The local bot runner now monkeypatches the Microsoft Agents SDK
to force unauthenticated mode, allowing the Bot Framework Emulator to drive the bot without requiring Azure
credentials.
### Removed
- 🗑️ **Bot-Driven User Provisioning Capability**: The `create_user` method in `KeycloakAdminService` and its
corresponding integration test were removed, aligning with the architectural decision to exclusively provision
Keycloak users via the web portal's first-broker-login flow.
______________________________________________________________________
## [v0.294.0] - 2026-06-11 - Swiss AI Hub Now Fully Open-Source with AGPL-3.0-or-later Sysadmin Plane
### Added
- 📄 **New Architectural Decision Record (ADR):** Introduced a comprehensive ADR detailing the strategic decision to
relicense the multi-tenant administration plane to AGPL-3.0-or-later, outlining its context, decision drivers, and
consequences.
### Changed
- 🔄 **Relicensed Multi-Tenant Administration Plane:** The `packages/sysadmin-api` and `packages/sysadmin-web`
components, previously proprietary, are now open-source under the **GNU Affero General Public License v3.0 or later
(AGPL-3.0-or-later)**. This change completes the transition of Swiss AI Hub to a fully open-source platform.
- 🔑 **Updated Licensing Model Documentation:** All project documentation, including the root `README.md`, `LICENSES.md`,
`CONTRIBUTING.md`, and internal architecture documents, has been thoroughly revised to reflect the new dual-license
model (Apache-2.0 for backend/SDK and AGPL-3.0-or-later for UI/administration).
- ⚡️ **Refined Sysadmin Package Rationale:** The justification for `sysadmin-api` and `sysadmin-web` existing as
separate packages now focuses purely on architectural and security boundaries, rather than licensing, as both the main
application and administration plane share the AGPL-3.0-or-later license.
- ⚙️ **Deployment Configuration Updates:** Docker Compose templates and generation scripts have been updated to reflect
the AGPL-3.0-or-later license for `sysadmin-api` and `sysadmin-web`, ensuring accurate license annotations in
deployment artifacts.
### Removed
- 🗑️ **Deprecated Proprietary License Identifiers:** All per-file `SPDX-License-Identifier` headers denoting proprietary
licenses have been removed from source files within `packages/sysadmin-api` and `packages/sysadmin-web` for
consistency with the rest of the monorepo, which relies on package-level `LICENSE` files.
- 🗑️ **Removed Proprietary Tags from Architecture:** The `#proprietary` tag has been eliminated from architecture
specifications and container definitions, aligning these views with the platform's fully open-source status.
______________________________________________________________________
## [v0.293.2] - 2026-06-10 - Improved Configuration Generation and Placeholders
### Added
- ✨ **Introduced UUID generation for environment setup:** Added a new `gen_uuid` function and `REPLACE_WITH_RANDOM_UUID`
placeholder to the `setup-env.sh` script, enabling automatic generation of UUIDs for configuration values.
### Changed
- 🔄 **Updated Rclone RC credential placeholders:** Renamed the default placeholders for Rclone RC user and password in
`.env.prod` to `REPLACE_WITH_RANDOM_STRING` for consistency and clarity with the automated generation process.
______________________________________________________________________
## [v0.293.0] - 2026-06-10 - Enhanced Version Visibility for API and UI
### Added
- ✨ **Service Version Reporting**: The API's `/health` and `/ready` endpoints now include the running service version in
their responses, improving operational visibility.
- ✨ **UI Version Display**: The web application now fetches and displays both its own and the API's service versions in
the user settings, clearly indicating if the UI and API are running on different versions.
- ✨ **OpenAPI Version Documentation**: The API's OpenAPI specification has been updated to include the service version,
making it programmatically accessible for clients.
### Changed
- 🔄 **Health Response Structure**: The core `HealthResponse` data transfer object has been extended to include a
dedicated `version` field.
- 🔄 **Web Build Process**: The web application's Dockerfile and Nuxt configuration have been updated to bake the service
version into the static bundle during build time, ensuring accurate version reporting.
______________________________________________________________________
## [v0.292.3] - 2026-06-10 - Enhanced Reliability for Agent Event Streams
### Fixed
- 🐛 Resolved a race condition that could cause **agent output streaming chunks** to be dropped, leading to incomplete or
blank answers in chat clients. This fix ensures all `DisplayEvent`s are processed before consumer teardown.
- 🔗 Guaranteed the **delivery of complete agent answers** by implementing a durable backstop that reconciles any missing
content from the final stop event, preventing empty assistant turns and associated errors.
### Added
- ✨ Introduced shared utility methods, **`iter_streamed_display_events`** and **`wait_for_stop_then_drain`**, in
`ChatService` to ensure all display events are gracefully drained and processed after an agent run completes.
- 📄 Documented the architectural decision for robust streaming in a new ADR: **"Drain Display-Event Streams Before
Consumer Teardown"**.
- 🧪 Added new **unit tests** to validate the correctness and resilience of the event draining and answer reconciliation
logic.
### Changed
- 🔄 Refactored **all streaming event consumers** (including OpenAI SSE, JSON, and native event streams) to utilize the
new graceful draining mechanisms, improving the consistency and reliability of agent output.
- 🚀 Updated **final content resolution** for OpenAI and JSON responses to include delta reconciliation, using the
terminal `StopEvent` as a source of truth to complete partially streamed or lost answers.
______________________________________________________________________
## [v0.292.2] - 2026-06-09 - Infrastructure Network Enhancements
### Changed
- 🚀 **Improved Backend Network Connectivity:** The core backend services are now connected to a dedicated `egress`
network across all deployment configurations, enhancing support for secure and managed outbound connections.
______________________________________________________________________
## [v0.292.0] - 2026-06-08 - Streamlined Python Distribution: PyPI Launch and Enhanced Documentation
### Added
- 🚀 **PyPI Publishing Workflow:** Introduced a new automated GitHub Actions workflow for building and publishing all
public Python SDK packages to PyPI, enabling wider distribution and easier installation.
- 🔐 **Distribution Artifact Validation:** Added a new Python script (`check_dist.py`) to enforce namespace integrity,
prevent secret leaks, and flag unexpectedly large files within PyPI packages, significantly enhancing release security
and quality.
- 📦 **`swiss-ai-hub` Meta-Package:** A new meta-package, `swiss-ai-hub`, has been added to simplify the installation of
the entire Swiss AI Hub Python SDK with a single command.
- 📄 **Package License Files:** Explicit `LICENSE` files have been added to each Python package (`agent`, `api`, `bot`,
`core`, `meta`, `pipeline`, `process`), ensuring clear license compliance for distributed artifacts (Apache-2.0 for
most, AGPL-3.0-or-later for `backup`).
### Changed
- 📝 **Comprehensive Package READMEs:** The `README.md` files for all Python SDK packages have been significantly
expanded and improved to provide detailed purpose, installation, usage, development, and production guidance, tailored
for PyPI consumers.
- ⚙️ **Standardized PyPI Metadata:** All `pyproject.toml` files across the Python SDK packages have been enhanced with
detailed descriptions, relevant PyPI classifiers, and comprehensive project URLs (Homepage, Repository, Documentation,
Issues) for improved discoverability and information.
- 🔗 **Pinned Internal Dependencies:** Python `pyproject.toml` files now explicitly pin inter-package dependencies (e.g.,
`swiss-ai-hub-agent` depending on `swiss-ai-hub-core`) to their exact version, ensuring consistent and predictable
builds.
- 🛠️ **Build System Configuration:** Adjusted `uv_build` settings in `pyproject.toml` for all Python packages to
correctly handle PEP 420 native namespaces during artifact creation, ensuring proper module loading.
- 🌐 **Public Image URLs in README:** All image references in the root `README.md` have been updated to use absolute
GitHub raw URLs, guaranteeing they display correctly on external platforms like PyPI.
- 🔒 **NPM Publish Workflow Environment:** The `publish-npm.yml` GitHub Actions workflow now uses an explicit `npm`
environment for enhanced security context during package publication.
- 📖 **Environment Variables Documentation:** The formatting and readability of the environment variables reference
documentation have been updated for better clarity.
- ⬆️ **Core Dependency Updates:** Several key dependencies in `swiss-ai-hub-core` have been upgraded, including
`mem0ai`, `langchain-neo4j`, `neo4j`, `rank-bm25`, and `starlette` (with a security floor to `1.0.1`), enhancing
functionality and addressing known vulnerabilities.
- 🐍 **`jambo` Internal Dependency:** The `jambo` dependency in `swiss-ai-hub-api` has been migrated to
`swiss-ai-hub-jambo`, reflecting an internal packaging change for consistent namespace usage.
- ⚡️ **Version Bump Script:** The `Makefile`'s `version-bump` target now correctly updates inter-package dependencies to
ensure all internal `swiss-ai-hub-*` packages reference the new version.
### Refactor
- 🧹 **Form Default Seeding Logic:** The web UI's `seedFormDefaults` function has been refactored by extracting dedicated
helper functions for handling group and repeater elements, improving code clarity and maintainability.
______________________________________________________________________
## [v0.291.13] - 2026-06-08 - Enhanced OpenAI Service Iframe Permissions
### Changed
- ⚡️ **Expanded OpenAI service iframe capabilities:** The embedded OpenAI service now supports additional browser
features such as **clipboard access**, **camera**, **screen capture**, **fullscreen mode**, **geolocation**, and
**autoplay**, alongside refined **microphone access**.
______________________________________________________________________
## [v0.291.12] - 2026-06-08 - Refined Skill Permissions for Enhanced Control
### Changed
- ⚡️ **Refined Skill Permissions:** The broad `mcp__*` skill wildcard has been replaced with a specific, enumerated list
of allowed `mcp` skills, enhancing control and clarity over which tools are accessible.
______________________________________________________________________
## [v0.291.11] - 2026-06-08 - Enhanced Changelog Documentation
### Changed
- ⚡️ **Improved Changelog Documentation Handling**: The documentation build script now correctly processes changelog
content by wrapping its body in a `v-pre` container. This change prevents build errors caused by special syntax (e.g.,
GitHub Actions expressions) within release notes, ensuring that content is rendered verbatim on the documentation site
while preserving the functionality of dynamic elements like copy/download buttons.
______________________________________________________________________
## [v0.291.10] - 2026-06-08 - Enhanced Security and Code Quality
### Added
- ✨ **Introduced CodeQL Analysis workflow:** A new GitHub Actions workflow has been added to perform automated static
code analysis, enhancing the security and overall code quality of the Python and JavaScript/TypeScript codebases.
______________________________________________________________________
## [v0.291.9] - 2026-06-08 - Simplified Access Control and Enhanced Identity Provider Setup
### Added
- 📄 **New Identity Provider Setup Documentation**: Introduced comprehensive guides for connecting external identity
providers to the Swiss AI Hub via Keycloak, starting with detailed instructions for Microsoft Entra ID (Azure AD).
- 🔐 **Azure App Registration Guide**: Provided step-by-step documentation for configuring Azure App Registrations,
including redirect URIs, required API permissions (`openid`, `email`, `profile`), and client secret management.
- 👥 **Azure User and Role Management Guide**: Added clear instructions on defining and assigning `AIHubAccess`
(mandatory for platform login) and `AIHubSysAdmin` app roles within Azure Entra ID to control user access.
### Changed
- 🔑 **Updated Superuser Role Configuration**: Modified default superuser roles (`SUPERUSER_ROLES_JSON`) to explicitly
include `AIHubAccess` as the base role for platform usage, aligning with the refined access control model.
- 📖 **Enhanced Authentication & Authorization Documentation**: Significantly updated the security documentation to
clarify the distinction between Keycloak Realm roles (`AIHubAccess`, `AIHubSysAdmin`) and platform-managed
tenant-scoped roles, providing a clearer understanding of the permission hierarchy.
### Removed
- 🗑️ **Streamlined Keycloak Realm Roles**: Deprecated and removed `AIHubAdmin`, `AIHubUser`, and `AIHubDeveloper` from
the Keycloak realm configuration. These roles are now intended to be managed as tenant-specific roles within the
platform's internal access management system.
- 🧹 **Simplified Identity Provider Mappers**: Removed corresponding identity provider mappers for the deprecated
`AIHubAdmin`, `AIHubUser`, and `AIHubDeveloper` roles, streamlining the IdP integration process.
______________________________________________________________________
## [v0.291.8] - 2026-06-08 - Streamlined Initial Navigation and Enhanced Authentication
### Added
- ✨ **Seamless Initial Navigation:** Introduced a new, dedicated middleware for the home route (`/`) to intelligently
determine and redirect users to their appropriate tenant, tenant selection, or previously visited page immediately
after login, ensuring a smoother start to their session.
- 🚀 **Global Loading Overlay:** Implemented a full-screen loading spinner that instantly appears when the initial
navigation logic is resolving, eliminating content flicker and providing clear visual feedback during critical
redirects.
- ⚙️ **Centralized Home Resolution State:** Added a shared state management system and client-side plugin to accurately
manage the global loading indicator, ensuring it correctly displays during initial route resolution and dismisses on
navigation completion or errors.
### Changed
- 🛡️ **Robust Authentication Handling:** Enhanced the authentication flow to automatically invalidate and remove
problematic user sessions when silent sign-in or token renewal encounters errors, improving stability and preventing
repeated authentication failures.
### Refactor
- 🧹 **Refined Initial Login Flow:** Streamlined the application's startup by relocating the logic for checking and
renewing user sessions from the OIDC client plugin to a global authentication middleware, allowing for more efficient
and non-blocking initialization.
- ⬆️ **Modernized Homepage Implementation:** Replaced the client-side `onMounted` logic for tenant fetching and
redirection on the homepage with a server-side (or early client-side) middleware, significantly improving the
responsiveness and consistency of the initial user experience.
______________________________________________________________________
## [v0.291.7] - 2026-06-03 - Enhanced Model Cost Reporting and API Robustness
### Added
- ✨ **Introduced Granular Search Context Cost Reporting:** Added a new data transfer object
(`SearchContextCostPerQueryDTO`) to capture and display search context costs broken down by low, medium, and high
context sizes, providing more detailed pricing information.
- 🦾 **Enhanced API Robustness for Model Information:** Implemented a new data validation mechanism that gracefully
handles unexpected data shapes in external model metadata feeds by logging warnings and dropping malformed fields,
preventing parsing failures for the entire model information.
### Changed
- 📊 **Updated Model Details Panel:** The **Model Details Panel** now presents search context costs with a more granular
breakdown (low, medium, and high context sizes), offering clearer insights into model pricing.
- 🌍 **Localized Search Context Cost Labels:** Updated internationalization files to include specific labels for low,
medium, and high search context costs in various languages (English, German, French, Italian).
- 🔄 **Revised Model Information Schema:** Modified the **`ModelInfoDTO`** to incorporate the new granular
`SearchContextCostPerQueryDTO` for search context costs and made the `mode` field optional, reflecting more flexible
model metadata.
______________________________________________________________________
## [v0.291.6] - 2026-06-03 - Core System Refinements and Performance Enhancements
### Changed
- 🚀 **Enhanced LLM Guard Performance:** Switched agent description and few-shot guards to use asynchronous structured
prediction, improving responsiveness and concurrency with Large Language Models.
- ⚡️ **Improved Concurrency for Reranking Nodes:** Blocking reranking operations are now offloaded to a separate thread
using `asyncio.to_thread`, preventing event loop starvation and boosting application performance.
- 📄 **Refined Error Logging:** Updated `MineruLoader` and Redis request hooks to use `logger.exception`, ensuring
comprehensive exception details are automatically included in logs for better debugging.
- 🛠️ **Corrected OpenTelemetry HTTPX Instrumentation:** Ensured asynchronous HTTPX request and response hooks are
properly defined as coroutine functions, enabling full observability for async HTTP communications.
### Refactor
- 🧹 **Optimized Dispatcher Event Handling:** The `_build_event_kwargs` method in agent and process dispatchers, as well
as the base dispatcher, has been made synchronous, streamlining event argument construction.
- 🔄 **Standardized Access Rule Prefixes:** Consolidated `aihub.admin` and `aihub.user` access rule prefixes into
dedicated constants, enhancing maintainability and clarity across authentication logic.
- 🔑 **Centralized Authentication Error Messages:** Moved common error messages in the authentication handler to
constants, improving consistency and simplifying future updates.
- ☁️ **Standardized S3 Service Error Messages:** Consolidated error messages for S3 anonymous file access service
operations into constants, improving consistency and maintainability.
- ⚙️ **Refined SonarQube Configuration:** Added specific ignore rules to `sonar-project.properties` to suppress
`python:S1192` (duplicated string literals) warnings for known-good patterns, such as lazy imports and MongoDB query
syntax, reducing false positives in code quality scans.
- 📝 **Improved RPC Success Attribute Handling:** Standardized the `rpc.success` attribute for NATS requester tracing
using a dedicated constant.
- 📚 **Improved Test Mock Clarity:** Added `noqa` comments to asynchronous mock methods in authentication utilities and
Milvus vector store tests to explicitly state why they remain async for testing purposes, improving code clarity for
linters.
- 🧹 **Minor Python Idiom Improvements:** Updated set creation from `set(generator_expression)` to `{comprehension}` in
`base_event` and `dispatchable_workflow` for more idiomatic Python.
______________________________________________________________________
## [v0.291.5] - 2026-06-02 - Enhanced FormKit Data Handling for Stability
### Changed
- 🔄 **Refined `getNestedValue` utility:** The `getNestedValue` function has been refactored to operate as a pure
read-only function, specifically for retrieving nested arrays. This change prevents unintended mutations of form data
during render-time and mitigates potential recursive render loops in reactive contexts. It now strictly returns an
array or an empty array if the specified path does not lead to an array.
### Fixed
- 🛠️ **Improved Repeater Initialization:** FormKit repeater fields now correctly materialize as an empty array during
form loading if their default value is `undefined`. This proactive initialization ensures a consistent array structure
for repeaters, preventing unexpected behavior and improving stability in reactive render contexts.
______________________________________________________________________
## [v0.291.4] - 2026-06-02 - Enhanced Prompt Reliability for Image Rendering
### Fixed
- 🐛 **Image Rendering in Prompts**: Resolved a Jinja `SecurityError` that occurred when attempting to render image URLs
(`pydantic.AnyUrl`) within RAG agent and guard context prompts. The rendering now correctly uses the `| string` filter
to safely convert URLs, ensuring images are displayed as intended across all supported languages.
______________________________________________________________________
## [v0.291.3] - 2026-06-02 - Stricter OAuth Role Defaults
### Changed
- 🔑 **Updated OAuth Allowed Roles Default:** The default `OAUTH_ALLOWED_ROLES` configuration for Open WebUI has been
updated across all deployment configurations. It now explicitly requires `AIHubAccess` and any configured admin roles
(`${OAUTH_ADMIN_ROLES_OPENWEBUI}`) instead of a wildcard (`*`), enhancing access control and improving the security
posture for OAuth integrations.
______________________________________________________________________
## [v0.291.2] - 2026-06-02 - GitHub Actions Reliability Improvement
### Fixed
- 🐛 **Corrected GitHub Actions `if` condition parsing:** Ensured proper evaluation of the `claude-code-review`
workflow's trigger conditions by explicitly wrapping the complex expression in `${{ ... }}`.
______________________________________________________________________
## [v0.291.1] - 2026-06-02 - Improved Code Quality Checks with SonarCloud Action v6
### Changed
- 🚀 **Updated SonarCloud Scan Action:** The GitHub Action for running SonarCloud code quality scans has been upgraded to
`v6.0.0`, enhancing reliability and ensuring compatibility with the latest SonarCloud features.
- 📄 **Documentation Refresh:** Internal documentation (`CLAUDE.md`) was updated to reflect the new version of the
SonarCloud scan action used in CI/CD workflows.
______________________________________________________________________
## [v0.291.0] - 2026-06-02 - 🚀 Web Package Stability, Deployment, and Publishing Overhaul
### Added
- 🚀 **Automated npm publishing workflow**: Introduced a dedicated GitHub Actions workflow (`publish-npm.yml`) to
streamline and automate the publishing of the `@swiss-ai-hub/web` package to npm, utilizing OIDC trusted publishing
for enhanced security.
- 🧪 **Web package build and pack verification**: Added a new GitHub Actions workflow (`verify-web-package.yml`) to
automatically build and validate the packaging of `@swiss-ai-hub/web` on every pull request, ensuring package
integrity and publishability before merge.
### Changed
- ⚙️ **Streamlined CI/CD branch triggering**: Modified several core CI/CD workflows (`analyze-test-pr`, `lint-pr`,
`semantic-pr`, `test-backup-e2e`) to exclusively trigger on the `main` branch, simplifying branching strategy and
pipeline execution.
- 📄 **Enhanced web package installation and dependency management documentation**: Significantly updated the
`packages/web/README.md` with detailed guidance on required dependency overrides (specifically for `vue` and
`primevue`) to prevent common runtime issues caused by multiple framework instances.
- 🔄 **Redesigned runtime configuration for static builds**: Introduced a new, more robust runtime configuration
mechanism for the `@swiss-ai-hub/web` layer, moving away from `NUXT_PUBLIC_*` environment variables to a dynamic
`/config.js` file generated at container startup, offering greater deployment flexibility for static single-page
applications.
- 🐳 **Updated Dockerfile and deployment examples**: Provided comprehensive and up-to-date Dockerfile and
`nuxt.config.ts` examples in the `packages/web/README.md` that reflect the new best practices for building, serving,
and configuring the static web package in production environments.
- ✨ **Integrated FormKit PrimeVue**: Added `@sfxcode/formkit-primevue` as a new dependency within the `packages/web`
package, enhancing its form building capabilities with PrimeVue components.
- ⬆️ **Updated PrimeVue peer dependency**: Bumped the recommended `primevue` peer dependency version to `4.5.5` to
ensure compatibility and leverage the latest updates for the UI component library.
______________________________________________________________________
## [v0.290.13] - 2026-06-02 - OAuth Default Role Access Update
### Added
- ✨ **Introduced `OAUTH_ALLOWED_ROLES` configuration:** Added the `OAUTH_ALLOWED_ROLES` environment variable, set to `*`
by default across Docker Compose configurations, to explicitly allow all roles from the OAuth provider, simplifying
initial setup for role-based access.
______________________________________________________________________
## [v0.290.12] - 2026-06-02 - Documentation Refinements and Link Consistency
### Changed
- 🔗 **Updated External README Links:** Transformed relative paths for licenses and contributing guidelines in the main
`README.md` to absolute GitHub URLs, ensuring links function correctly across all platforms.
- 📄 **Minor Documentation Formatting:** Improved the display of the Dagster UI URL in the `packages/backup` README for
better readability.
### Refactor
- 🧹 **Standardized Internal Documentation Paths:** Performed a comprehensive review and adjustment of numerous relative
links throughout the German and English documentation, enhancing navigation and consistency within the platform guides
and SDK.
- 📚 **Streamlined Authentication Documentation:** Removed outdated architectural decision record (ADR) links from
authentication-related documentation, simplifying the content and focusing on current implementation details.
______________________________________________________________________
## [v0.290.11] - 2026-06-01 - Enhanced Documentation and Licensing Clarity
### Added
- 📄 **New API Tokens Documentation**: A dedicated guide has been added explaining how to generate, use, and revoke
personal API tokens for REST API authentication, including steps within the Swagger UI.
- ✨ **Comprehensive SDK Licensing Rationale**: New documentation now details the platform's mixed-license model,
clarifying the rationale behind using Apache 2.0 for the backend and AGPLv3 for the user interface, and its
implications for proprietary agent development and community contributions.
- 📄 **Licensing Clarifications**: The `LICENSES.md` and `README.md` files have been updated with explicit explanations
of the mixed-license model, emphasizing the permissive backend and copyleft UI.
- 🔑 **UI Extensibility Licensing Note**: A new licensing note has been added to the UI extensibility documentation,
explicitly detailing the AGPL-3.0 implications for modifying and offering the UI as a network service.
### Changed
- 🔄 **Refined Ecosystem Model Documentation**: The documentation for the ecosystem model has been significantly revised
to provide a clearer and more in-depth explanation of the platform's collaborative approach and licensing strategy.
- 📝 **Clarified Agent Behaviors**: Extensive updates have been made across several agent documentation pages (**Company
Knowledge Agent**, **Instructed Assistant**, **Teachable Assistant**, **Retrieval Agent**, **Document Navigation
Assistant**, and **MCP Tool Agent**) to provide clearer descriptions of their purpose, workflows, capabilities,
limitations, and setup instructions.
- 📝 **Improved UI Extensibility Guide**: The UI extensibility documentation has been substantially rewritten to offer
more detailed insights into architectural foundations, custom service implementation, development workflows,
deployment, and strategic value.
- 📝 **Updated MCP Tools Usage in SDK**: The SDK documentation for using MCP tools has been rephrased for better clarity,
particularly regarding connection configuration and authentication modes.
- 📄 **Minor Documentation Updates**: Small adjustments and rephrasing have been applied to the **Web Search** and
**Environment Variables** documentation pages for improved readability and consistency.
- 📝 **API Tokens Documentation Enhancement**: The English version of the API Tokens documentation has been updated to
include the revoke endpoint in its lifecycle table.
______________________________________________________________________
## [v0.290.10] - 2026-06-01 - Enhancing Project Governance: CLA and Contributor Guidelines
### Added
- ✨ **Contributor License Agreement (CLA):** Introduced a formal Individual Contributor License Agreement
(`CONTRIBUTOR_AGREEMENT.md`) to clarify intellectual property rights for contributions.
- 🚀 **Automated CLA Checks:** Implemented a new GitHub Actions workflow (`cla.yml`) to automatically verify CLA
signatures for pull requests, streamlining the contribution process.
- 📄 **Pull Request Template:** Added a standard pull request template (`pull_request_template.md`) to guide contributors
in providing necessary information, improving PR quality and review efficiency.
### Changed
- ✍️ **Updated Contributing Guidelines:** Expanded `CONTRIBUTING.md` with a new section detailing the Contributor
License Agreement process and instructions for signing it.
- ⚖️ **Refined Licensing Notice:** Updated the project's `NOTICE` file to provide a more general description of the
mixed-license model, referencing `LICENSES.md` for full details.
______________________________________________________________________
## [v0.290.9] - 2026-06-01 - Next-Gen Agents and Operational Excellence
### Added
- 🦾 **New Agent Blueprints**: Introduced a suite of powerful, pre-built agent blueprints for diverse use cases:
- 📄 **Document Intelligence Assistant**: A highly configurable Retrieval-Augmented Generation (RAG) agent that answers
questions from internal documents with citations, replacing the previous generic RAG agent with advanced
capabilities including context-sufficiency guards and user/organization memory.
- 🤝 **Company Knowledge Agent**: An extension of the Document Intelligence Assistant that incorporates
human-in-the-loop for user consent and bot-in-the-loop for expert consultation, capturing human replies as
organizational knowledge.
- 🧑💻 **Expert Coordinator Agent**: Designed to relay questions to human experts via Slack or Teams, verify their
responses, and store them as reusable organizational knowledge. Channel configuration is now managed directly within
the agent's profile.
- 🧠 **Instructed Assistant**: A foundational chat assistant that follows plain-text system prompts for focused tasks
like drafting, translation, or style-guided responses without document retrieval.
- 📚 **Teachable Assistant**: An agent that learns behavior from a few examples (few-shot prompting), making it ideal
for tasks requiring specific response styles or formats.
- 🔍 **Retrieval Agent**: A headless, composable building block that performs document retrieval from a knowledge base
without generating an answer, providing raw context for custom workflows.
- 🧭 **Document Navigation Assistant**: A routing agent that intelligently identifies the most relevant knowledge
bases/namespaces for a user's question, confirms the selection with the user, and then delegates to a RAG agent.
- 🛠️ **MCP Tool Agent**: Enables agents to connect to external Model Context Protocol (MCP) servers and execute
actions in other systems, such as creating tickets, querying databases, or sending messages.
- ⚙️ **Automated PostgreSQL Maintenance**: Integrated continuous weekly cleanup and monthly repack jobs for the Dagster
PostgreSQL database, ensuring long-term operational stability by managing log growth and freeing disk space.
- 📄 **Comprehensive Environment Variable Reference**: Added a new, automatically generated documentation page listing
all environment variables, their purpose, and consumer services, clarifying setup and configuration.
- 🌐 **Self-Hosted Web Search**: Documented the new privacy-focused web search feature, powered by a self-hosted SearXNG
meta-search instance for Open-WebUI, including its default search engine list and customization options.
- 🔗 **MCP Tooling for SDK Agents**: Provided detailed SDK guidance on connecting custom agents to external MCP servers
and invoking their tools, including `McpClientConfig` and various authentication modes.
- 💬 **External Tool Connectivity Overview**: Expanded the Agents overview documentation to include a new section
explaining how agents can interact with external systems using the Model Context Protocol (MCP).
### Changed
- 🔄 **Updated Documentation Structure**: Significantly reorganized the agent documentation, reordering blueprint pages
for improved logical flow and discoverability.
- 📝 **Refined German Translations**: Numerous minor textual and stylistic improvements across German documentation for
enhanced clarity and consistency.
- 🔑 **Clarified Tenant Deletion Rules**: Updated multi-tenancy documentation to state that any tenant can be deleted as
long as at least one remains, clarifying the lack of special deletion protection for the initial startup tenant.
- ⚙️ **Standardized Startup Tenant Variables**: Renamed environment variables related to initial tenant configuration
(e.g., `AIHUB_DEFAULT_TENANT_NAME` to `AIHUB_STARTUP_TENANT_NAME`) for clearer semantics.
- 🌐 **Updated Network Egress Requirements**: Removed Jina AI from the list of external service dependencies in the
network requirements documentation.
- 📄 **Improved Main Page Tagline**: Updated the tagline on the main documentation landing page for better clarity and
impact.
- 🔄 **Refined Docs Sync Script**: Improved the `sync-docs.sh` script to precisely control which `README.md` files are
synchronized into the `docs/6_code_deep_dive` section, preventing unintended content.
### Removed
- 🗑️ **Deprecated Agent Documentation**: Removed the outdated "RAG Agent" and "Expert Asking Agent" documentation, as
their functionalities have been superseded and enhanced by the new agent blueprints.
- 🗑️ **Removed Internal Tooling Documentation**: Cleaned up documentation pages previously generated from internal
`.claude`, `.github/actions`, and `infra/deployment` directories, streamlining the documentation scope.
______________________________________________________________________
## [v0.290.8] - 2026-06-01 - Introducing a Mixed-License Model and Enhanced Contribution Guidelines
### Added
- 📄 **New `NOTICE` File:** Introduced a new `NOTICE` file to formally outline the project's mixed-license model,
providing clear attribution and a detailed license breakdown for different components.
- 👨💻 **Author Information for Web Package:** Explicitly added the author (`bbv Software Services AG`) to the
`packages/web/package.json` metadata.
### Changed
- ⚖️ **Revised Project Licensing Model:** The project now operates under a mixed-license model; `packages/web` and
`packages/backup` are explicitly licensed under AGPL-3.0-or-later, while most other packages remain Apache-2.0 and
`sysadmin-*` packages are proprietary.
- 📝 **Updated Contribution Guidelines:** The `CONTRIBUTING.md` file has been significantly updated to reflect the new
mixed-license model, detailing that contributions are subject to the target package's license and requiring
contributors to sign a Contributor License Agreement (CLA).
- 📅 **Extended Copyright Information:** Updated the root `LICENSE` file and relevant package `README.md` files to extend
the copyright period to `2026` and specify `bbv Software Services AG` as the copyright holder.
- 🔗 **Enhanced License Documentation:** `CLAUDE.md`, `LICENSES.md`, `packages/backup/README.md`, and
`packages/web/README.md` have been updated to accurately reflect and reference the detailed per-package licensing
matrix and the new `NOTICE` file, ensuring greater transparency.
______________________________________________________________________
## [v0.290.7] - 2026-05-29 - Introducing API Token Management and Documentation
### Added
- 📄 **New Documentation for API Tokens:** Added comprehensive documentation guiding users on how to generate, manage,
and use personal API tokens (`sk-`) for authenticating REST API requests, ensuring secure and long-lived access for
scripts and integrations.
______________________________________________________________________
## [v0.290.6] - 2026-05-29 - Backup Service Refinements and Code Quality Enhancements
### Changed
- 📄 **Refined SonarQube Configurations:** Updated SonarQube issue suppression rules for backup services (Neo4j, Valkey,
Milvus) to accurately reflect intentional design patterns and improve the clarity of code quality analysis.
### Refactor
- 🧹 **Optimized Regular Expressions:** Streamlined regex patterns in ClickHouse and PostgreSQL backup services by
adopting `\w` shorthand and the `re.ASCII` flag, enhancing readability and precision.
- ⚡️ **Enhanced Milvus Backup Selection:** Improved the efficiency of Milvus backup selection logic by replacing
`sorted()[-1]` with `max()`, leading to a more direct and performant approach for identifying the latest backup.
______________________________________________________________________
## [v0.290.5] - 2026-05-28 - Dependency Security Enhancement
### Security
- 🔑 **Bolstered Dependency Security:** Introduced a minimum version constraint for the `starlette` dependency
(`>=1.0.1`) to mitigate a known vulnerability in earlier versions and enhance overall project security.
______________________________________________________________________
## [v0.290.4] - 2026-05-27 - Development Environment Update
### Changed
- ⚡️ **Upgraded `pnpm` Package Manager:** The monorepo's package management tool has been updated to `pnpm` version
`10.20.0`, enhancing dependency resolution and optimizing the overall development workflow.
______________________________________________________________________
## [v0.290.3] - 2026-05-27 - Platform Refinements and Developer Experience Improvements
### Added
- 🦾 **Backup Service SonarCloud Analysis:** Enabled SonarCloud scanning for the `backup` package to improve code quality
and maintainability.
- 📄 **Backup Service SonarCloud Configuration:** Introduced `sonar-project.properties` for the `packages/backup`
service, configuring its code analysis.
### Changed
- ⚙️ **SonarCloud Rule Exclusions:** Updated SonarCloud configuration to exclude a rule
(`Web:ItemTagNotWithinContainerTagCheck`) for `Notification/Item.vue` to accommodate new semantic HTML structures.
### Refactor
- ✨ **Enhanced Accessibility for Input Components:** Improved form accessibility across various components by adding
explicit `input-id` attributes to input fields and `for` attributes to their corresponding labels. This includes:
- Agent Selector
- Chips Input
- Vector Store Input
- Knowledge Namespace Create and Edit Modals
- Memory Edit components
- User Settings Language Selector
- 🚀 **Optimized Data Retrieval Performance:** Improved the efficiency of identifying active items and filtering events
by transitioning from array `filter` and `includes` methods to more performant `find` and `Set.has` operations,
notably in:
- Tenant details page navigation
- Event list filtering
- Agent, Knowledge document, and Thread navigation composables
- 🧹 **Codebase Modernization and Cleanup:**
- Modernized asynchronous operations and error handling in core functionalities, including the application's health
check and authentication callbacks, by adopting `async/await` syntax.
- Replaced `window.location.origin` and `window.localStorage` with `globalThis` for improved cross-environment
compatibility in the OIDC client plugin.
- Standardized Node.js built-in module imports (`node:url`) in Nuxt configuration.
- Streamlined conditional checks with optional chaining syntax for better readability.
- Removed redundant or empty CSS selectors and unnecessary empty `