MRH Trowe, a German commercial and industrial insurance broker, gave 400 employees self-service access to AI agents in their first production month. The deployment had to satisfy BaFin compliance, GDPR data residency requirements, and internal security policies without creating 400 separate agent instances or requiring IT intervention for every new user.
This is rare public documentation of compliance-first agent architecture in regulated financial services. Most case studies skip the hard parts: how you enforce user-level access control when multiple employees share infrastructure, where reasoning traces and tool call logs actually live, and what happens when agents need to process customer PII but can't persist sensitive context.
The Stack
MRH Trowe combined three components:
- LibreChat: Open-source UI layer that handles user authentication, session management, and conversation history
- Strands Agents: Orchestration framework that manages agent lifecycle, tool registration, and execution boundaries
- Amazon Bedrock AgentCore: AWS-managed service providing foundation model access, guardrails, and audit logging with German region data residency
The division of responsibility matters. LibreChat enforces who can access which agents. Strands Agents controls what those agents can do. Bedrock AgentCore ensures all model interactions stay within Frankfurt region boundaries and generate compliance-ready audit trails.
Data Residency in Practice
Data residency is not just "run it in eu-central-1." Every piece of agent state has a location:
| Data Type | Storage Location | Retention Policy | Access Control |
|---|---|---|---|
| User credentials | LibreChat database (Frankfurt) | Indefinite | Role-based |
| Conversation history | LibreChat database (Frankfurt) | 90 days | User-scoped |
| Agent reasoning traces | Bedrock AgentCore logs (Frankfurt) | 1 year | Admin-only |
| Tool call parameters | Strands Agents ephemeral memory | Session-only | Agent-scoped |
| Customer PII | External systems (not cached) | N/A | Zero retention |
The critical constraint: agents can read customer insurance records to answer questions, but they cannot persist that data in conversation history or reasoning traces. Every tool call that touches PII must complete within a single request-response cycle. No caching, no embeddings, no fine-tuning on customer data.
This forces a specific architectural pattern. Agents use stateless tool calls to external systems of record. They receive just enough context to answer the current question, then discard it. The next question starts fresh.
Multi-Tenant Access Control
400 users sharing agent infrastructure creates a tenant separation problem. You cannot give everyone admin access to Strands Agents. You cannot let users see each other's conversations. You cannot allow one user's malicious prompt to escape into another user's session.
LibreChat solves the first layer with standard user authentication and session isolation. Each user gets their own conversation threads, and the UI enforces read/write boundaries.
Strands Agents handles the second layer with agent-level permissions. Each agent declares which tools it can access and which data sources it can query. A user might have access to multiple agents, but each agent has a fixed capability boundary that users cannot expand through prompt injection.
Bedrock AgentCore provides the third layer: model-level guardrails that filter inputs and outputs regardless of which user or agent made the request. If a prompt tries to extract PII, the guardrail blocks it before the model sees it. If a model response contains sensitive data patterns, the guardrail redacts them before returning to the user.
Authorization Flow
When a user asks an agent to look up a customer policy:
- LibreChat authenticates the user and validates session token
- User submits query through LibreChat UI
- LibreChat forwards request to Strands Agents with user context
- Strands Agents checks agent permissions: does this agent have access to policy lookup tools?
- If yes, Strands Agents calls the tool with sanitized parameters
- Tool queries external policy system (outside agent infrastructure)
- Tool returns minimal response data (no PII caching)
- Strands Agents passes response to Bedrock AgentCore for reasoning
- Bedrock AgentCore applies output guardrails
- Filtered response returns through Strands Agents to LibreChat
- LibreChat stores conversation turn (without raw PII) and displays to user
Every step generates an audit log entry. BaFin compliance requires proving who accessed what data, when, and why. The three-layer architecture makes this auditable without custom instrumentation.
Deployment Shape
The production deployment runs entirely in AWS Frankfurt region:
# Simplified deployment topology
region: eu-central-1
services:
librechat:
compute: ECS Fargate
database: RDS PostgreSQL (encrypted at rest)
auth: Cognito user pool
strands_agents:
compute: Lambda functions (per-agent)
state: DynamoDB (session metadata only)
secrets: Secrets Manager (tool credentials)
bedrock_agentcore:
service: Managed (AWS-operated)
models: Claude 3.5 Sonnet, Claude 3 Haiku
guardrails: Custom PII filters + prompt injection detection
logs: CloudWatch Logs (1-year retention)
networking:
vpc: Private subnets for compute
endpoints: VPC endpoints for Bedrock, Secrets Manager
egress: NAT gateway for external tool calls
security:
encryption: TLS 1.3 in transit, KMS at rest
iam: Least-privilege per service
waf: Rate limiting + SQL injection rules
The Lambda-per-agent pattern in Strands Agents allows independent scaling and failure isolation. If one agent has a runaway tool call, it does not block other agents. Each Lambda has its own IAM role with exactly the permissions needed for its declared tools.
Observability and Failure Modes
The stack generates three observability streams:
- User activity: LibreChat logs every login, query, and session timeout
- Agent execution: Strands Agents logs tool calls, execution time, and errors
- Model interactions: Bedrock AgentCore logs every prompt, completion, and guardrail trigger
Common failure modes:
Tool timeout: External policy system takes longer than Lambda timeout (15 minutes). Strands Agents returns partial response and logs incomplete execution. User sees "lookup timed out, try narrowing your query."
Guardrail block: User asks agent to "email me all customer names." Bedrock guardrail detects bulk PII extraction attempt and blocks request. User sees "this request violates data policy."
Permission denial: User tries to access an agent configured for a different department. LibreChat checks user role, denies access at UI layer. User never sees the agent in their available list.
Session expiration: User leaves conversation open for 30 minutes. LibreChat expires session token. Next query requires re-authentication. No conversation history lost, but user must log in again.
The architecture assumes external systems are unreliable. Every tool call has a timeout, retry budget, and fallback message. Agents never block indefinitely waiting for external data.
Cost and Scale Characteristics
First-month usage with 400 users:
- Average 12 queries per user per day
- 80% queries answered without tool calls (pure reasoning)
- 20% queries required 1-3 tool calls to external systems
- Median response time: 2.3 seconds
- P95 response time: 8.7 seconds (tool calls included)
Cost breakdown:
- Bedrock AgentCore: $0.003 per input token, $0.015 per output token
- Lambda (Strands Agents): $0.20 per million requests
- ECS Fargate (LibreChat): Fixed monthly cost for UI hosting
- RDS PostgreSQL: Fixed monthly cost for conversation storage
The biggest cost driver is model token usage, not infrastructure. Optimizing prompt templates and reducing unnecessary context in tool calls has more impact than scaling compute.
Security Boundaries
The architecture enforces four security boundaries:
- User-to-UI: LibreChat authentication prevents unauthorized access
- UI-to-orchestration: Strands Agents validates every request comes from authenticated LibreChat session
- Orchestration-to-model: Bedrock AgentCore applies guardrails before and after model inference
- Agent-to-tools: Each tool call requires explicit IAM permission and runs in isolated Lambda context
Breaking any single boundary does not compromise the others. A prompt injection that bypasses Bedrock guardrails still cannot access tools the agent lacks IAM permissions for. A compromised tool credential cannot access conversation history stored in LibreChat database.
This defense-in-depth approach is expensive (more moving parts, more latency) but necessary for BaFin compliance. A single-layer security model would not pass audit.
Technical Verdict
Use this architecture when:
- You need to give non-technical employees agent access in a regulated industry
- Data residency and audit trails are legal requirements, not nice-to-haves
- You have 100+ users who need self-service access without IT tickets
- Your agents must query external systems but cannot cache sensitive data
- You can tolerate 2-10 second response times for tool-augmented queries
Avoid this architecture when:
- You have fewer than 50 users (the multi-tenant complexity is not worth it)
- Your agents need to learn from conversation history or fine-tune on user data (the zero-retention policy prevents this)
- You need sub-second response times (the three-layer validation adds latency)
- Your compliance requirements allow simpler single-region, single-tenant deployments
- You lack the operational maturity to monitor three separate systems and correlate their logs
The MRH Trowe stack is not the fastest or cheapest way to deploy agents. It is the most auditable way to deploy agents in a German financial services context. That trade-off makes sense for regulated industries. It makes less sense for startups or internal tools.
Top comments (0)