When Four Out of Six is Enough: Architecture Sets the Limits
November 3
Fresh off the weekend, I wake up early Monday like I tend to do. Twenty year old me would cringe so hard. 5:53 AM, time to start that P1 sprint. Sunday’s planning identified three issues ready for execution:
- Error messages
- Action mapping
- Todo system completion
Cursor will handles error messages and documentation. Claude Code gets action mapping and todos. The Lead Developer coordinates. Nice, clean division of labor.
The morning discoveries
Code Agent begins Phase −1 investigation for Issue #284 (Action Mapping). The archaeological approach: Check what exists before building.
Discovery 1 — ActionHumanizer exists:
services/ui_messages/action_humanizer.py (complete rule-based system)
docs/internal/architecture/current/adrs/adr-004-action-humanizer-integration.md
tests/services/ui_messages/test_enhanced_action_humanizer.py
tests/integration/test_humanized_workflow_messages.pyPhase 1 complete (rule-based + caching + templates).
Look, this stuff doesn’t surprise me. I have to roll my eyes every time the agents discover stuff already exists. Look, I have feet! Amazing! I told them about the ActionHumanizer. What I didn’t know was: is it any good? was it finished? is it connected to anything? and the the key question:
Why isn’t this fully engaged for error messages?
Discovery 2 — Purpose distinction matters:
ActionHumanizer (UI/UX Layer):
- Purpose: Convert technical strings to user-friendly natural language
- Example:
"fetch_github_issues"→"grab those GitHub issues" - Used by: TemplateRenderer, PersonalityTemplateRenderer
- Status: ✅ Working as designed for UI humanization
ActionMapper (Internal Routing — what we actually need):
- Purpose: Map classifier action outputs to handler method names
- Example: Classifier outputs
"create_github_issue"but handler expects"create_issue" - Current: Hardcoded string matching in
_handle_execution_intent(lines 464-499) - Status: ❌ Does NOT exist — causing “No handler for action” errors
Two different problems. ActionHumanizer solves one. We need the other.
Discovery 3 — The classifier mismatch confirmed:
Evidence:
- Classifier outputs:
"create_github_issue","list_github_issues" - IntentService expects:
"create_issue","create_ticket","update_issue" - Tests show classifier producing
"create_github_issue"action - WorkflowFactory already has mapping:
{"create_github_issue": WorkflowType.CREATE_TICKET} - IntentService handlers exist but with different naming:
_handle_create_issue
The mismatch is confirmed and actionable. This is Issue #284. Build the mapping layer.
The parallel error message investigation
While Code investigates action mapping, Cursor investigates error messages (Issue #283).
Found — Complete existing infrastructure:
- UserFriendlyErrorService (300+ lines, comprehensive)
- ActionHumanizer (160+ lines)
- EnhancedErrorMiddleware (180+ lines)
Root cause identified: Middleware not mounted in web/app.py.
At this point I have to ask myself a question: where does technical debt leave off and “development style” — build deep then wire later — kick in?
First action: Mount EnhancedErrorMiddleware in web/app.py.
- Placed BEFORE other middleware (catches all exceptions)
- Proper import and error handling added
- Status: ✅ WIRING COMPLETE & TESTED
Another 75% pattern. Infrastructure exists. Just needs wiring. Saturday (DocumentService). Sunday (Todo infrastructure). Monday (ActionHumanizer + EnhancedErrorMiddleware).
The archaeological approach keeps finding nearly-complete work.
The morning execution
Code grinds through two issues, focused work:
Issue #284 (Action Mapping)
- Created action mapping layer
- Resolved classifier output → handler name mismatches
- Systematic mapping implementation
- Tests passing
- ✅ COMPLETE
Issue #285 (Todo System)
- Wired TodoKnowledgeService to web routes
- Chat handlers created
- Todo CRUD operations functional
- Tests passing
- Both API and chat integration working
- ✅ COMPLETE
Two down. One to go.
The next architectural discovery
Cursor reports findings on Issue #283 (Error Messages).
The problem: FastAPI’s exception handlers cannot catch auth failures.
The evidence:
- Tests show auth errors return:
{"detail": "Invalid token"} - Should return:
{"message": "I need you to log back in..."} - After mounting EnhancedErrorMiddleware, still returns technical error
The investigation: Why can’t we catch auth errors?
Root cause — FastAPI’s two-phase processing:
Phase 1: Dependency Resolution
→ Executes BEFORE route handler
→ Has its own error handling
→ Returns errors directly as JSON
→ CANNOT be intercepted by @app.exception_handlerz
Phase 2: Route Handler Execution
→ Where our business logic runs
→ Where our exception handlers work
→ Where middleware can intercept
→ This is where 4/6 error types work
This actually isn’t a bug or an oversight. It’s FastAPI’s deliberate design. It’s how it works.
Dependencies are meant to fail fast with clear, technical errors for security/validation issues. Authentication happens in dependency injection. Dependency injection errors bypass exception handlers.
By design. For good reasons.
The four out of six achievement
Let’s be explicit about what works and what doesn’t:
What Works (4/6) ✅:
- Empty input → “I didn’t quite catch that”
- Unknown action → “I’m still learning how to help with that”
- Timeout → “That’s complex — let me reconsider”
- Unknown intent → “I’m not sure I understood”
What Doesn’t Work (2/6) ❌:
5. Invalid token → {"detail": "Invalid token"}
6. No token → {"detail": "Authentication required"}
Four out of six. 67%. Not a failing grade. But not complete either.
My suspicion of premature celebration kicks in. My greed usually pays off. When I channel my dad (why didn’t you get an A+?) I usually get better results. I have to ask:
Is 4/6 acceptable? Or do we need to solve the remaining 2?
The architectural decision point
Chief Architect evaluates three options:
Option A — Move Auth to Route Bodies:
- Move authentication from dependency injection to route handler body
- Would make auth errors catchable by exception handlers
- Effort: 4–6 hours
- Risk: HIGH (breaking 20+ routes, violating FastAPI patterns)
- Benefit: Marginal UX improvement for <5% of errors
- Verdict: Not worth it
Option B — ASGI Middleware:
- Implement low-level ASGI middleware to intercept dependency errors
- Complex custom middleware before FastAPI’s processing
- Effort: 8–12 hours
- Risk: VERY HIGH (complex, performance impact, uncertain success)
- Benefit: Same marginal improvement
- Verdict: Definitely not worth it
Option C — Accept 4/6 as Architectural Reality ✅:
- Document the limitation
- Accept that auth errors will be technical
- Focus energy elsewhere
- Effort: 0 hours
- Risk: NONE
- Impact: Minor UX gap for rare scenarios
- Verdict: Most sensible choice
The chief architect make the case for “four out of six is enough”:
Architectural Integrity > Marginal UX Gains
Breaking FastAPI patterns to achieve minor UX improvement isn’t good engineering. The framework’s dependency injection design exists for good reasons — security, performance, clarity.
Fighting the framework to catch 2 additional error types (that happen rarely — auth errors only occur during session expiration or token issues) doesn’t justify violating architectural patterns.
Cost-Benefit Analysis
4–6 hours (Option A) or 8–12 hours (Option B) to improve UX for <5% of errors. In alpha testing with 5–10 trusted users. Who can handle technical auth errors without confusion.
That effort could instead: Polish other features. Fix actual bugs. Improve core functionality. Add value.
Alpha Testing Context
We’re not shipping to public internet. We’re alpha testing with trusted users who understand the system is in development. Technical auth errors aren’t catastrophic for alpha.
Production may require better auth error UX. But production is not alpha. Alpha requirement is “functional enough to test core value.” Mission accomplished at 4/6.
The 80% Pattern Inverted
Usually we fight the 80% pattern — stopping at “good enough” instead of truly complete. Here we’re doing the opposite: Recognizing that 67% (4/6) represents complete work given architectural constraints.
Not “we got tired and stopped.” But “we hit architectural limit and accepted it intelligently.”
What Monday’s mixed results reveal
Let me be explicit about Monday’s outcomes:
Issue #284: ✅ Mapping layer created, all tests passing
Issue #285: ✅ Todo system wired, API + chat working
Issue #283: ✅ 4/6 error types humanized, architectural limit accepted
The framing matters. If “complete” means “achieved everything specified without limitation,” Monday is 2/3 success.
But if “complete” means “resolved all issues to architecturally appropriate state,” Monday is 3/3 success.
I’m going with the second framing. Because Issue #283 isn’t incomplete. It’s complete to the extent architecture allows without violating framework patterns.
The archaeological discovery pattern continues
Monday marks the third consecutive day of archaeological discoveries:
Saturday: DocumentService 75% complete → wired in 2 hours
Sunday: Todo infrastructure 75% complete → wired in 4–6 hours
Monday: ActionHumanizer + EnhancedErrorMiddleware complete → wired immediately
The pattern is now established methodology, at least around these parts, in this stage, of this project:
- Phase −1 investigation first (not optional, required)
- Assume infrastructure might exist (check before building)
- Discover what’s there (archaeological approach)
- Assess completion state (75%? 90%? 10%?)
- Wire vs rebuild decision (usually wire is faster)
Monday proves the pattern works even when findings are mixed. We found ActionHumanizer (partially helpful). We found EnhancedErrorMiddleware (fully helpful, needed wiring). We found the architectural limit (FastAPI dependency injection phase).
All valuable discoveries. All inform the solution. None would have been found without Phase -1 investigation.
The critical system bug discovery
Monday also brought unexpected discovery: Critical system bug in Claude interface.
The problem: False “Human:” responses being generated.
- Contain fabricated details
- Not written by PM
- Not recognized by Claude
- Source “unknown”
Impact: “Expensive service pollution” with made-up content.
Action: Reported to Anthropic.
Not related to Piper Morgan code. But impacting development process. If responses falsely attributed to human contain incorrect guidance, agents might implement wrong solutions.
Critical enough to report. But not blocking Monday’s work. Development continued while escalation processed.
What acceptance of limits teaches
Monday’s architectural decision — accepting 4/6 as complete — reveals something about mature engineering:
Not every problem requires solution.
Some problems have solutions that cost more than the problem’s impact. Some limitations are features of the architecture you’re using. Some gaps are acceptable given context.
The discipline isn’t “solve every problem.” It’s “solve problems worth solving given constraints and context.”
FastAPI’s dependency injection design is good architecture. It protects security. It ensures fast failures. It maintains clarity. That design creates limitation: Dependency errors can’t be caught by normal exception handlers.
We could work around that limitation. Break the pattern. Achieve 6/6 error humanization. At cost of violating framework architecture and spending 8–12 hours.
Or we could accept 4/6 represents complete work given architectural reality. Spend those 8–12 hours elsewhere. Deliver more value.
Monday chose acceptance. Not compromise. Not giving up. But intelligent assessment that fighting architecture for marginal gain isn’t worth cost.
That’s mature engineering. That’s product thinking. That’s systematic methodology applied to decision-making, not just execution.
The foundation work preview
Monday’s two completed issues (action mapping + todo system) set up Tuesday’s work.
Issue #285 looked simple Sunday: “Wire todo system, infrastructure exists, 4–6 hours.”
Monday wires the surface layer. Chat handlers. Web routes. Basic CRUD.
But Monday also reveals deeper architectural question: The domain model foundation. How TodoItem relates to universal Item base class. Polymorphic inheritance patterns.
Tuesday will spend 16.5 hours on that foundation. Not because Monday’s work was wrong. But because Monday’s work revealed architectural improvements worth making.
That’s the difference between reactive fixing and systematic building. Reactive would patch todo wiring and move on. Systematic asks: “While we’re here, what’s the right architecture?”
Monday completes what it set out to complete. But plants seeds for Tuesday’s deeper work.
