Solving the Formal Specification Complexity Problem in Intent-Based Programming
By Ben Houston, 2025-05-01
Why Specifications Have Historically Failed
For decades, the dream of formal specifications has remained just that -- a dream. Despite significant academic and industry efforts, formal specification languages like Z, VDM, and Alloy have never achieved widespread adoption outside niche applications. This failure has deep roots:
-
Precision Overhead: Traditional specifications required exhaustive detail to be actionable, often becoming as complex as the code they aimed to replace.
-
Semantic Gap: Bridging natural requirements ("users should log in securely") to formal terms required specialized expertise most teams lacked.
-
Inflexibility: Specifications became brittle when requirements evolved, requiring complete reworking rather than incremental refinement.
-
Cognitive Burden: Developers needed to master both programming languages and formal specification languages -- effectively doubling their learning curve.
These challenges led to the widespread abandonment of formal specification approaches in favor of agile methodologies that embraced code as the primary artifact. But the emergence of Large Language Models (LLMs) fundamentally changes this equation.
The LLM Advantage: Why Specifications Can Work Now
Modern LLMs possess capabilities that address the core limitations of traditional specification approaches:
-
Common Sense Reasoning: LLMs can infer "obvious" implementation details without explicit specification, dramatically reducing the required level of detail.
-
Domain Knowledge: LLMs already understand standard patterns in software development -- authentication flows, CRUD operations, data models -- allowing specifications to focus on business-specific elements.
-
Natural Language Understanding: Specifications can now be written in augmented natural language rather than formal languages, eliminating the translation burden.
-
Contextual Generation: LLMs can maintain consistency across generated artifacts even when specifications are incomplete.
These capabilities enable a new approach I call "Sufficient Specification" -- providing just enough intent to guide generation without becoming overwhelmed by detail.
Iterative Specification: A Practical Approach
The core of solving specification complexity lies in embracing an iterative approach to increasing specificity. Rather than attempting to specify everything upfront, we can:
-
Start with Core Intent: Begin with minimal specifications that capture the essence of what you want.
-
Generate and Review: Produce an initial implementation and identify areas where the generated code doesn't match your expectations.
-
Iteratively Refine: Add specificity only to aspects that need correction, allowing the LLM to continue inferring the rest.
This approach leverages a key insight: not all aspects of an application require the same level of specification detail. Let's explore this concept through a concrete example:
# Initial Minimal Specification feature: user-management intent: Allow users to register, log in, and manage their profiles auth: type: email-password
The LLM can generate a complete implementation from this minimal specification, inferring reasonable defaults for everything not explicitly specified. Upon review, you might find that:
- The password requirements aren't strong enough
- The profile fields don't include all needed information
- The registration flow is missing email verification
Rather than rewriting the entire specification, you simply add specificity to these aspects:
# Refined Specification feature: user-management intent: Allow users to register, log in, and manage their profiles auth: type: email-password password: minLength: 12 requireSpecialChars: true requireNumbers: true verification: email-link profile: fields: - name - location - bio - profilePicture
This iterative approach maintains a crucial balance: it provides control where needed while leveraging LLM inference for everything else. The specification remains significantly less complex than the resulting implementation while still guaranteeing that critical requirements are met.
Addressing Complexity Peaks with Provider Patterns
Even with an iterative approach, some aspects of software development involve inherent complexity that can be challenging to capture in specifications. For these cases, we need an escape hatch -- what I call "generator patterns."
Providers are pre-written implementation guides for common complex subsystems that can be referenced in specifications:
# Using a Provider feature: payments generator: stripe/standard-checkout options: currencies: [USD, EUR, GBP] methods: [credit, debit, paypal]
Instead of specifying the intricate details of payment processing, the specification simply references a generator that encapsulates this complexity. Providers can be:
- Standard Library Providers: Common implementations maintained by the platform.
- Community Providers: Shared implementations for popular frameworks and services.
- Local Project Providers: Custom implementations specific to a project.
This pattern allows teams to encapsulate complexity where appropriate while maintaining the benefits of declarative intent for the majority of their application.
Balancing Inference and Specification
A critical insight for making this approach work is understanding the appropriate balance between inference and explicit specification. This balance depends on several factors:
-
Business Criticality: The more business-critical a component, the more explicit its specification should be.
-
Novelty: Standard patterns can rely more heavily on inference; novel approaches need more specificity.
-
Team Consensus: Areas where the team has strong opinions about implementation should be more explicitly specified.
The key is recognizing that not everything needs the same level of specification detail. By allowing LLMs to handle the "obvious" parts through inference, developers can focus their specification efforts on what truly matters for their application.
Conclusion: The Pragmatic Middle Path
The approach outlined here represents a pragmatic middle path between two extremes:
-
Pure Code: Where implementation details overwhelm intent, creating maintenance challenges.
-
Formal Specification: Where specification complexity matches or exceeds implementation complexity.
By embracing "Sufficient Specification" with LLMs, we can capture intent without drowning in details. The iterative nature of the approach means developers don't need to get everything right upfront -- they can start simple and add specificity only where needed.
This represents a fundamental shift in how we think about software development -- from writing code to declaring intent. And unlike previous attempts at specification-driven development, the intelligence embedded in LLMs makes this approach practical for the first time.
The question is no longer whether specifications can work -- it's how quickly we can develop the tools and practices to support this new paradigm.