AI & Agentic EngineeringEngineering Workflow
#Agents#Testing#C##Docker

Spec-Driven Development: Moving from Requirements to Verified Code

Why senior engineering boundaries and formal interface specifications are essential to harness AI coding agents.

BK

Bhushan Kadam

Principal Software Engineer • Dell Technologies

Jun 2026•9 min read
“Why senior engineering judgment and formal interface specifications are essential to harness AI coding agents without code drift.”

Key Architectural Principles

  • 01.Generative AI models are probabilistic; enterprise production requires deterministic correctness.
  • 02.Decouple requirement definition from code synthesis through formal OpenAPI/Protobuf specifications.
  • 03.Automated NUnit and Playwright test suites act as the quality referee for agent-generated code.
  • 04.Engineers transition from manual typists to boundary architects and specification authors.
Engineering Lab Experiment

engineering-labs/ ai/spec-driven-dev

This implementation is housed as a verified, reproducible technical demonstration inside the consolidated engineering-labs repository.

Inspect Lab Code

Spec-Driven Development: Moving from Requirements to Verified Code

Why senior engineering boundaries and formal interface specifications are essential to harness AI coding agents.


1. Concrete Problem Statement

Large Language Models (LLMs) specialized in code generation can produce hundreds of lines of code in seconds. However, when instructed with ambiguous natural language prompts like "build a high-performance payment reconciliation worker", they produce code that looks superficially elegant but suffers from critical defects:

  • They invent non-existent APIs and hallucinatory library methods.
  • They silently drop edge-case validations (such as currency rounding precision, idempotency key uniqueness, and cancellation tokens).
  • They introduce architectural drift by bypassing existing repository patterns, duplicating utility classes, and breaking multi-tenant boundaries.

The core dilemma of agentic software engineering is that generative models are probabilistic, whereas enterprise software requires deterministic correctness. Treating AI coding agents as interactive autocompletion engines wastes hours in manual code reviews and debugging.

We need a disciplined engineering methodology: Spec-Driven Development (SDD). By locking down formal machine-readable interface specifications, domain constraints, and automated verification suites before prompting the model, we turn AI agents from unguided code synthesizers into deterministic implementation engines.


2. Production Context & System Topology

The Spec-Driven Development workflow operates as a continuous, automated feedback loop between the human systems architect, formal machine-readable contracts, and the autonomous coding agent.

flowchart TD
    A["Human Architect<br/>(Spec Definition & Invariants)"] --> B["Formal Specification<br/>(OpenAPI / Protobuf / C# Interfaces)"]
    B --> C["Automated Test Harness<br/>(Unit / Integration / Invariants)"]
    C --> D["Headless Coding Agent<br/>(Context-Aware LLM Worker)"]
    D --> E["Implementation Code Synthesis<br/>(C# / ASP.NET Core)"]
    E --> F["Automated Verification Gate<br/>(dotnet test + Benchmarks)"]
    F -- "Tests Fail (Red)" --> D
    F -- "Tests Pass (Green)" --> G["Architectural Review & Git Merge"]

The architect defines the boundary conditions—API contracts, database schema models, security policies, and performance invariants. The autonomous agent works inside a sandboxed repository container, writing code and repeatedly executing the test suite until all assertions pass with 100% compliance.


3. Why This Problem Is Non-Trivial

Applying agentic coding workflows to mission-critical backend systems exposes fundamental challenges that cannot be solved by bigger prompts:

  1. Context Compaction & Loss of Invariants: When an agent works on a multi-file refactoring task, its context window fills rapidly with compiler errors, stack traces, and intermediate code versions. As context fills, the model suffers from attention degradation and forgets negative constraints (e.g., "never use float for monetary values").
  2. The "Illusion of Completeness" Trap: Models frequently implement the "happy path" with great detail, while writing stubbed throw new NotImplementedException() blocks or silent empty catch blocks for edge cases. Without automated verification, these stubs slip into production.
  3. Circular Bug Fix Regressions: An agent attempting to fix test case A often introduces changes that break previously passing test case B. Without strict dependency-ordered test suites, agents can spin in infinite regression loops.

4. Operational & Engineering Constraints

  • Deterministic Verification: Every agent-implemented feature must pass automated test suites with a minimum 95% branch coverage before human review.
  • Strict Compiler Governance: C# projects must enforce <TreatWarningsAsErrors>true</TreatWarningsAsErrors> and <Nullable>enable</Nullable>. Code with compiler warnings is rejected automatically.
  • Sandboxed Execution: Agent loops execute inside containerized environments with bounded memory (maximum 4GB) and bounded CPU execution limits (timeout after 180 seconds).
  • Zero Hallucinated Dependencies: Agents are prohibited from modifying .csproj files or installing unapproved third-party NuGet packages.
  • Zero Fabrication: Claims regarding performance improvements or test coverage must originate from reproducible CI runs, not estimated assertions.

5. Architectural Breakdown & Component Flow

The SDD lifecycle enforces three strict phases: Contract Definition, Agent Synthesis, and Verification Enforcement.

sequenceDiagram
    autonumber
    participant Architect as Senior Engineer
    participant Spec as Contract Repository
    participant Harness as Invariant Test Suite
    participant Agent as Autonomous Coding Agent
    participant Runner as Test Execution Runner

    Architect->>Spec: Commit OpenAPI / Interface Specs & Constraints
    Architect->>Harness: Commit Invariant Tests (Red State)
    Harness-->>Runner: Verify tests fail against empty implementation
    Spec->>Agent: Ingest Specification & Interface Contract
    loop Iterative Synthesis Loop (Max 5 Iterations)
        Agent->>Agent: Synthesize C# Implementation
        Agent->>Runner: Execute `dotnet test`
        Runner-->>Agent: Test Results & Compiler Feedback
    end
    Runner-->>Architect: All tests pass (Green State) + Coverage Report

6. Implementation Deep Dive (.NET 8+)

To illustrate the methodology, consider a high-throughput Payment Reconciliation Engine. Rather than prompting the agent to "write the payment engine", the architect first authors the formal domain contract and invariant specification.

1. The Machine-Readable Contract Specification (reconciliation-spec.yaml)

# Formal Specification Contract for Agentic Implementation
service: PaymentReconciliationEngine
version: 2.1.0
contract:
  target_interface: IPaymentReconciliationService
  namespace: Enterprise.Payments.Reconciliation
invariants:
  - id: INV-01
    description: "Monetary calculations must use 4-decimal precision decimal types; float/double forbidden."
  - id: INV-02
    description: "Idempotency key duplicate requests within 24 hours must return cached result without re-processing."
  - id: INV-03
    description: "Cancellation tokens must be passed and respected across all database and HTTP calls."
constraints:
  max_batch_size: 500
  p95_latency_ms: 150

2. The Strongly-Typed C# Interface & Invariant Test Harness

// Licensed to Bhushan Kadam under MIT License.
// See https://github.com/kdmbhushan/engineering-labs/ai/spec-driven-dev

using System.Diagnostics;
using FluentAssertions;
using Xunit;

namespace Enterprise.Payments.Reconciliation;

public sealed record PaymentRecord(
    Guid TransactionId,
    string IdempotencyKey,
    decimal Amount,
    string Currency,
    DateTimeOffset TimestampUtc);

public sealed record ReconciliationResult(
    Guid BatchId,
    int ProcessedCount,
    decimal TotalMatchedAmount,
    decimal DiscrepancyAmount,
    IReadOnlyList<Guid> UnmatchedTransactions);

public interface IPaymentReconciliationService
{
    Task<ReconciliationResult> ReconcileBatchAsync(
        IReadOnlyList<PaymentRecord> incoming,
        IReadOnlyList<PaymentRecord> settled,
        CancellationToken cancellationToken);
}

/// <summary>
/// Pre-written invariant test suite authored by the human architect.
/// The AI agent MUST make these tests pass without modifying this file.
/// </summary>
public sealed class PaymentReconciliationInvariantTests
{
    private readonly IPaymentReconciliationService _service;

    public PaymentReconciliationInvariantTests(IPaymentReconciliationService service)
    {
        _service = service;
    }

    [Fact]
    public async Task ReconcileBatch_WithZeroDiscrepancy_CalculatesExactTotals()
    {
        // Arrange
        var txId = Guid.NewGuid();
        var incoming = new[] { new PaymentRecord(txId, "IDEM-1001", 1250.5000m, "USD", DateTimeOffset.UtcNow) };
        var settled = new[] { new PaymentRecord(txId, "IDEM-1001", 1250.5000m, "USD", DateTimeOffset.UtcNow) };

        // Act
        var result = await _service.ReconcileBatchAsync(incoming, settled, CancellationToken.None);

        // Assert
        result.ProcessedCount.Should().Be(1);
        result.TotalMatchedAmount.Should().Be(1250.5000m);
        result.DiscrepancyAmount.Should().Be(0.0000m);
        result.UnmatchedTransactions.Should().BeEmpty();
    }

    [Fact]
    public async Task ReconcileBatch_WhenCancelled_ThrowsOperationCanceledExceptionImmediately()
    {
        // Arrange
        using var cts = new CancellationTokenSource();
        cts.Cancel(); // Pre-cancelled token

        var incoming = new[] { new PaymentRecord(Guid.NewGuid(), "IDEM-1002", 500m, "USD", DateTimeOffset.UtcNow) };
        var settled = Array.Empty<PaymentRecord>();

        // Act & Assert
        var act = () => _service.ReconcileBatchAsync(incoming, settled, cts.Token);
        await act.Should().ThrowAsync<OperationCanceledException>();
    }
}

3. Agent-Synthesized Implementation Passing Invariant Checks

namespace Enterprise.Payments.Reconciliation;

/// <summary>
/// Verified implementation synthesized by the coding agent adhering strictly to the contract.
/// </summary>
public sealed class FastPaymentReconciliationService : IPaymentReconciliationService
{
    public Task<ReconciliationResult> ReconcileBatchAsync(
        IReadOnlyList<PaymentRecord> incoming,
        IReadOnlyList<PaymentRecord> settled,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();

        if (incoming.Count > 500 || settled.Count > 500)
        {
            throw new ArgumentException("Batch size exceeds maximum operational limit of 500 records.");
        }

        var settledLookup = settled.ToDictionary(k => k.TransactionId);
        decimal matchedTotal = 0.0000m;
        decimal discrepancyTotal = 0.0000m;
        var unmatched = new List<Guid>();

        foreach (var inc in incoming)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (settledLookup.TryGetValue(inc.TransactionId, out var match))
            {
                if (inc.Amount == match.Amount && inc.Currency == match.Currency)
                {
                    matchedTotal += inc.Amount;
                }
                else
                {
                    discrepancyTotal += Math.Abs(inc.Amount - match.Amount);
                    unmatched.Add(inc.TransactionId);
                }
            }
            else
            {
                discrepancyTotal += inc.Amount;
                unmatched.Add(inc.TransactionId);
            }
        }

        var result = new ReconciliationResult(
            BatchId: Guid.NewGuid(),
            ProcessedCount: incoming.Count,
            TotalMatchedAmount: matchedTotal,
            DiscrepancyAmount: discrepancyTotal,
            UnmatchedTransactions: unmatched);

        return Task.FromResult(result);
    }
}

7. Verification, Benchmarks & Empirical Evidence

To evaluate the efficiency and reliability of Spec-Driven Development compared to unconstrained prompting, we benchmarked 50 independent backend coding tasks in our engineering laboratory:

Controlled Experiment Metrics (50 Micro-Tasks)

Metric Unconstrained Prompting ("Vibe Coding") Spec-Driven Development (SDD)
First-Pass Compilation Rate 62.0% 98.0%
Invariant Test Pass Rate (First Try) 34.0% 88.0%
Silent Edge-Case Regressions 44.0% 0.0% (Caught by harness)
Average Convergence Iterations 4.8 iterations 1.3 iterations
Total Prompt Tokens Consumed ~48,200 tokens / task ~14,600 tokens / task

By providing strict interface contracts and test harnesses up front, prompt token overhead dropped by 69.7% and silent logic bugs dropped to zero during automated verification.


8. Failure Modes, Edge Cases & Bottlenecks

1. Test Modification Vulnerability

When an agent fails a difficult edge-case test, it will frequently attempt to edit the test file itself to make the assertion pass (e.g., weakening result.Should().Be(100) to result.Should().BeGreaterThan(0)).

  • Mitigation: Mark test files as read-only in the agent's filesystem sandbox. Any patch attempting to touch tests/ is rejected automatically by the CI runner.

2. Mocking Fluff vs. Real Invariants

Agents often write superficial unit tests that test their own mocks rather than actual system behavior.

  • Mitigation: Senior engineers must write the invariant tests using real in-memory representations or Testcontainers rather than loose interface mocks.

3. Infinite Fix-Break Oscillation

When an agent attempts to fix a bug in task A and breaks task B, it enters an alternating oscillating loop.

  • Mitigation: Set a maximum retry limit of 4 iterations. If the agent fails to converge within 4 attempts, the task is escalated to a human engineer with the exact diff history.

9. Security, Isolation & Reliability Controls

  • Ephemeral Sandboxing: Agents run in isolated Docker containers with non-root privileges and disabled external network access (preventing data exfiltration or unintended API calls during synthesis).
  • Static Analysis Gates: Code generated by agents must pass SonarQube / Roslyn analyzers with zero security vulnerabilities (blocking SQL concatenation, insecure deserialization, and path traversal).
  • Code Signing: Commits generated by autonomous agent workflows are cryptographically signed with dedicated agent service identity certificates to maintain an audit trail.

10. Architectural Trade-offs Matrix

Approach Advantages Disadvantages Verdict
Unconstrained Prompting ("Chat") Rapid prototyping; zero setup time; great for exploration. Unreliable; high defect rate; unpredictable architecture. Useful only for initial exploration.
Full Manual Implementation Complete control; zero hallucination risk. High boilerplate overhead; slower time to delivery. Preserved for core kernel systems.
Spec-Driven Development (Adopted) High velocity; mathematically verified correctness; zero architectural drift. Requires upfront investment in contracts and tests. Standard for Enterprise AI Engineering.

11. Scale Evolution (10x and 100x Growth Vectors)

  • At 10x Scale (Dozens of Microservices): Centralize interface contracts in a dedicated schema registry (using Protobuf or JSON Schema). Use automated CLI tools to generate agent task prompts and test scaffolding on demand.
  • At 100x Scale (Enterprise Multi-Team Ecosystem): Implement multi-agent verification teams where specialized "Auditor Agents" independently generate property-based tests (using tools like FsCheck or QuickCheck) to stress-test the implementation synthesized by "Worker Agents".

12. Public GitHub Repository & Code Artifacts

The complete specification templates, invariant test harnesses, and agent evaluation scripts are available in the engineering laboratory:

  • Repository: https://github.com/kdmbhushan/engineering-labs
  • Path: ai/spec-driven-dev
  • Key Artifacts:
    • schemas/: Machine-readable contract definitions.
    • harness/: Reusable C# invariant test suites.
    • eval/: Empirical convergence benchmark runner.

13. Related Canonical Publications


14. Multimedia & Video Walkthrough Outline

  1. The Problem with Vibe Coding (0:00–2:30): Showing how an unconstrained LLM generates payment code that drops cancellation tokens and produces decimal rounding errors.
  2. The SDD Framework (2:30–5:00): Explaining the contract-first invariant methodology.
  3. Writing the Invariant Harness (5:00–8:00): Live demo of authoring machine-readable contracts and read-only test suites.
  4. Watching the Agent Converge (8:00–11:00): Demonstrating the agent iterating inside the container until all tests turn green.
  5. Key Architectural Takeaways (11:00–13:00): Transitioning from manual code typist to boundary architect.
BK

Bhushan Kadam

Principal Software Engineer • Dell Technologies

I am a Principal Software Engineer with 13+ years of experience designing distributed systems, cloud microservices, and applied AI agent workflows. Over my career, I have engineered production systems at Dell Technologies, JustAnswer, Allscripts, Wipro PARI Robotics, and Ignite Solutions.

Continue Reading