Skip to main content
This is Week 6 of “Building with AI” - a 10-week journey documenting how I use multi-agent AI workflows to build a production-grade SaaS platform.This week: The perfect counter-example to Week 5. Instead of asking AI to fix cascading errors, I showed it our architecture docs and asked “how should we design this?” The result: a complete AWS client factory with 39 tests, 2,364 lines of code, in 3 days. When AI has good inputs, it excels.Previous: Week 5: When AI Fails

Watch the 60-Second Summary

Week 6: Breaking changes and macro evolution

The Setup: After Week 5’s Humbling

Coming off Week 5’s disaster (30 commits to fix one breaking change), I needed a win. But more importantly, I needed to understand when AI works and when it doesn’t. Week 5’s lesson: AI is terrible at reactive debugging of cascading errors. Week 6’s hypothesis: Maybe AI is good at proactive design when given proper constraints? The test case: Our 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. The experiment: Could multi-agent AI analyze our existing architecture decisions and design a unified client factory?

What We Built This Week

The Challenge

By January 2026, the platform needed a unified way to manage AWS clients that:
  1. Enforced our 4 operational scopes (platform, tenant, capsule, operator)
  2. Prevented cross-tenant data leaks automatically
  3. Handled cross-account assume-role properly
  4. Migrated 3 existing crates without breaking production
Traditional approach: 2 weeks of design + implementation + migration AI-assisted approach: 3 days (1 day design, 2 days migration)

The Multi-Agent Discovery

Here’s what was different from Week 5: Week 5 (reactive): “Fix these 214 compilation errors” Week 6 (proactive): “Given our isolation constraints in ADR-0010, how should we manage AWS clients?”
The key difference: I didn’t ask AI to build something. I asked it to design something given our constraints.Week 5: AI fixing problems it created Week 6: AI designing solutions based on documented requirementsCompletely different outcomes.

What AI Did

The Evaluator agent analyzed ADR-0010 (our data isolation architecture doc) 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 insight: Instead of developers remembering to prefix table names, make the client enforce it at the type level.

What Human Did

I provided the architectural constraints from ADR-0010, reviewed the proposed client hierarchy, and added one critical piece AI missed: credential caching for assume-role operations. AI doesn’t know that AWS STS assume-role calls add 200-500ms latency. That’s operational knowledge from production experience.

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 (AI’s Best Discovery)

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. This is the opposite of Week 5’s hallucinated error types. Here, AI designed a type-safe API that prevents errors instead of creating them.

What Went Wrong (And How We Fixed It)

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.
But notice: This was a design gap, not a cascading error. We caught it in review before any code was written. Week 5’s problems came from reactive fixing; Week 6’s problems were caught proactively.

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. And here’s the key: Unlike Week 5’s migration, we did this atomically. One crate at a time. Each crate fully migrated and tested before moving to the next. Week 5’s mistake: Try to migrate everything at once, fix errors incrementally. Week 6’s success: Migrate one crate completely, verify, move on.

Week 6 vs Week 5: The Contrast

Task: Fix 214 compilation errors from macro changeAI Approach: Fix one error at a time, commit after eachResult: 30 commits, 24 hours, still had errorsWhy it failed:
  • AI had no global view of the problem
  • Each fix created new errors
  • Optimized locally, not globally
  • Hallucinated solutions when stuck

Key Learnings This Week

What worked: Giving AI our ADR-0010 (data isolation architecture) let it design a solution that automatically complied with our constraints.Why it worked: ADRs are structured, complete, and describe “why” not just “what.” Perfect for AI analysis.New practice: Before asking AI to build infrastructure, write an ADR documenting the constraints. Then let AI design within those boundaries.Contrast with Week 5: Week 5 had no design doc. Just “fix these errors.” No wonder it failed.
What we did differently: Evaluator → design review → implementation → verification per crateWhy it worked: Caught the credential caching issue in design review, before any code was written.Week 5 mistake: Implement macro change, then try to fix all affected code reactively.Week 6 success: Design client factory, review with human, implement one crate at a time atomically.
The insight: No human would have looked at ADR-0010 and immediately thought “4 scopes = 4 client types.”Why AI saw it: AI is excellent at pattern matching between documentation and code structure.The value: We got a more elegant design than we would have built manually. The capsule client’s automatic prefixing was AI’s idea, not ours.Caveat: AI still needed human to add credential caching. Pure pattern, but missing operational optimization.
What we did: Migrate 3 crates one at a time, each fully tested before moving on.Why it worked: Each crate was either old (working) or new (fully migrated). No intermediate broken states.Contrast with Week 5: Tried to update 4 crates simultaneously. Created 30 commits of broken intermediate states.The principle: For systematic refactoring, migrate completely or not at all. No partial migrations.

Metrics: Week 6 By The Numbers

Development time:
  • Time without AI: ~2 weeks (design + implement + migrate)
  • Time with AI: 3 days (1 day design, 2 days migration)
  • Savings: 78%
Migration time per crate:
  • auth: 6 hours
  • crm: 8 hours
  • catalog: 4 hours
Total: 18 hours vs estimated 40 hours manual

Actionable Takeaways

Based on Week 5’s failure and Week 6’s success:
  1. Write ADRs before asking AI to design - Document your constraints, isolation boundaries, naming conventions. Let AI design within those constraints.
  2. Use Evaluator for design, not debugging - AI excels at “given constraints, propose solution.” AI fails at “fix these cascading errors.”
  3. Atomic migration strategy - Migrate one component completely, test, then move on. Never commit broken intermediate states.
  4. Add operational knowledge - AI designs pure patterns. Humans add caching, performance optimizations, production battle-tested improvements.
  5. Review before implementing - Catch design gaps (like credential caching) before writing code. Much cheaper than fixing post-implementation.
Pro tip: When migrating to a new pattern, use the multi-agent workflow from Week 2:
  1. Evaluator plans the migration for ONE crate
  2. Builder executes that crate’s migration
  3. Verifier confirms tests still pass
  4. Repeat for next crate
We migrated 3 crates in 2 days this way. No cascading errors. No week-long debugging sessions.

The Redemption Arc

Week 4: AI excels at systematic work (107 commits, everything green) Week 5: AI fails at reactive debugging (30 commits, cascading errors) Week 6: AI excels at proactive design (2,364 lines, 0 production bugs) The pattern:
AI + Good Inputs (ADRs, clear requirements) = Excellent results
AI + Bad Inputs (fix these errors) = Disaster
AI + No Inputs (hallucination) = Unpredictable
Week 6 proved that Week 5’s failure wasn’t “AI is bad at Rust” or “AI can’t do systems programming.” It was: AI is bad at reactive debugging without holistic context. It proved: AI is excellent at design when given proper constraints.

What’s Next

Week 7 Preview: Taking the AWS client factory and the multi-agent workflow, we’ll tackle configuration governance. Another proactive design challenge where AI can analyze our config hierarchy and generate middleware that enforces it automatically. The hypothesis: If ADR-driven design worked for AWS clients, it should work for config management too.

Discuss This Week

Have you built multi-tenant infrastructure? How do you enforce isolation boundaries? Share your AWS client patterns or ask questions about the ADR-driven design approach.
Disclaimer: All examples are from personal projects. No proprietary code or employer-specific patterns included.