Skip to main content
This article is part of the “Building with AI” series documenting my journey using multi-agent AI workflows to build production systems.All examples are from personal projects and do not represent employer technologies.

The Challenge

By January 2026, my SaaS platform had sprawled across 9 Rust crates using 16 different AWS SDKs. Each crate created AWS clients ad-hoc wherever they needed them. DynamoDB clients in auth handlers. S3 clients in upload endpoints. SQS clients scattered everywhere. The Problem: No consistent pattern for credential management, no scope enforcement, and worst of all—possible cross-tenant data access because clients had no concept of isolation boundaries. AI Focus: Could multi-agent AI analyze our existing architecture decisions and design a unified client factory? System Example: A scope-aware AWS client factory with 4 client types enforcing data isolation at the SDK level.

The Multi-Agent Discovery

I didn’t ask AI to “build an AWS client factory.” Instead, I showed it our architectural decision record (ADR-0010) about data isolation and asked: “How should we manage AWS clients given these constraints?”
The Evaluator agent analyzed ADR-0010 and discovered we had 4 distinct operational scopes:
  • Platform-wide (metrics, logs, shared infrastructure)
  • Tenant-scoped (customer-specific resources)
  • Capsule-scoped (SDLC environments: dev/staging/prod)
  • Operator (cross-account provisioning)
It proposed matching each scope to a dedicated client type that enforces isolation rules automatically.

The Four Client Types

The pattern AI discovered maps operational scopes to client types:
// Platform-wide resources shared by all tenants
let factory = AwsClientFactory::from_env().await?;
let platform_db = factory.platform_dynamodb();

// Table name: "events" (no prefix)
// Use for: cross-tenant metrics, system logs, platform config

The Capsule Pattern

The most interesting discovery was the capsule client. AI noticed our ADR required SDLC isolation (dev data shouldn’t appear in prod), and proposed automatic table prefixing:
impl CapsuleClient<DynamoDbClient> {
    /// Returns capsule-prefixed table name per ADR-0010
    pub fn table_name(&self, base: &str) -> String {
        format!("{}_{}", self.capsule.capsule_code, base)
    }

    /// Validates partition key includes capsule boundary
    pub fn validate_pk(&self, pk: &str) -> Result<()> {
        if !pk.contains(&format!("CAPSULE#{}", self.capsule.capsule_id)) {
            return Err(IsolationViolation::CrossCapsule);
        }
        Ok(())
    }
}
Before AI: Developers had to remember to prefix table names. After AI: The client enforces it at compile time.

What Went Wrong

Mistake: The initial AI implementation didn’t include credential caching for cross-account operations.Why it failed: AI designed the operator client assuming instant AWS STS assume-role calls. In reality, assume-role adds 200-500ms latency.How we fixed it: Added a Moka cache with 55-minute TTL (AWS sessions last 60 minutes). Human knowledge of AWS operational characteristics.Lesson: AI designs pure patterns. Humans add performance optimizations based on production experience.

Migration Strategy

We migrated 3 crates in Phase 1 to validate the pattern: Before (auth crate):
// Scattered throughout the crate
let config = aws_config::load_from_env().await;
let dynamodb = aws_sdk_dynamodb::Client::new(&config);

// Hardcoded table name
let table_name = "platform_auth_sessions";  // WRONG: no capsule isolation
After (auth crate):
// Single factory, injected via dependency
let factory = AwsClientFactory::from_env().await?;
let capsule = extract_capsule(&req)?;
let client = factory.capsule_dynamodb(&capsule);

// Automatic prefixing
let table_name = client.table_name("sessions");  // "PRODUS_sessions"
Impact:
  • auth crate: 14 files changed
  • crm crate: 22 files changed
  • catalog crate: 8 files changed
Each migration eliminated ~600 lines of client creation boilerplate.

Key Learnings

AI Strength

AI excels at pattern extraction from documentation. It read ADR-0010 and immediately understood we needed 4 client types matching 4 scopes. No human would have made that connection that quickly.

AI Weakness

AI doesn’t know operational characteristics like assume-role latency or credential caching needs. It designs “correct” patterns but misses performance optimizations.

Human Role

Humans provide the constraints (ADRs), review the design, and add production battle-tested optimizations AI can’t infer from docs.

Process Insight

ADRs are the key to AI-assisted architecture. When AI can read your architectural decisions as structured documents, it designs solutions that comply with them automatically.

Actionable Takeaways

If you’re building multi-tenant infrastructure with AI:
  1. Write ADRs first - Document your isolation boundaries, naming conventions, and scope rules before asking AI to implement anything.
  2. Let AI discover patterns - Don’t say “build a factory.” Say “given these constraints (ADR), how should we manage clients?”
  3. Add performance late - Let AI design the pure pattern first, then add caching/optimization based on your operational knowledge.
Pro tip: When migrating to a new pattern, use the multi-agent workflow: Evaluator plans the migration, Builder executes 1 crate at a time, Verifier checks each crate’s tests still pass. We migrated 3 crates in 2 days this way.

Metrics

  • Time without AI: ~2 weeks (design + implement + migrate)
  • Time with AI: 3 days (1 day design, 2 days migration)
  • Savings: 78%

Resources & Further Reading


Next in This Series

Week 6: How configuration governance middleware eliminated 100% of manual config lookups using the same ADR-driven approach.

Week 6: Configuration Governance

The middleware pattern that made configuration hierarchical and automatic

Discussion

Share Your Experience

Have you built multi-tenant infrastructure? How do you enforce isolation boundaries?Connect on LinkedIn or comment on the YouTube Short

Disclaimer: This content represents my personal learning journey using AI for a personal project. It does not represent my employer’s views, technologies, or approaches.All code examples are generic patterns or pseudocode for educational purposes.