Ai Pair Programming Patterns

Production-grade guide to ai pair programming patterns covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

Ai pair programming is a collaborative workflow where an AI agent acts as a real-time coding partner to a human developer, dynamically suggesting, writing, reviewing, and refining code. It matters when developers face complex refactors, unfamiliar codebases, or tight deadlines—especially in systems that evolve rapidly, where context is fragmented and cognitive load is high. This pattern emerges naturally in modern CI/CD pipelines, monorepos, and polyglot environments, where the AI agent complements human intuition with scalable, consistent, and persistent knowledge.

Writing New Functions

When a developer starts writing a new function, the AI pair proactively suggests signatures, docstrings, and implementation bodies. The AI reads the surrounding code and project-wide patterns—such as naming conventions, error handling idioms, and common library usage—before proposing code.

The agent listens to the developer's editor via language server protocol (LSP), receiving real-time updates from the editor’s cursor position and open files. For example, when a developer types:

def calculate_total(items: List[Dict]) -> float:
    # 🟨 cursor here, type: float
    total = 0.0
    for item in items:
        total += item["price"] * item["quantity"]
    return total

The AI agent, running as a codepair service, detects the function signature and emits a completion suggestion with a textDocument/completion request:

{
  "jsonrpc": "2.0",
  "method": "textDocument/completion",
  "params": {
    "textDocument": { "uri": "file:///src/cart.py" },
    "position": { "line": 4, "character": 12 },
    "context": {
      "triggerKind": 1,
      "triggerCharacter": "."
    }
  },
  "id": 1
}

The AI responds with a set of completions, including a full function body:

{
  "jsonrpc": "2.0",
  "result": {
    "isIncomplete": false,
    "items": [
      {
        "label": "calculate_total",
        "kind": 3,
        "detail": "Calculate total value of a shopping cart",
        "documentation": "Given a list of items with price and quantity, returns the sum of all item values.\n\nExample:\n\n    >>> calculate_total([\n    ...     {'price': 10.0, 'quantity': 2},\n    ...     {'price': 5.0, 'quantity': 3}\n    ... ])\n    35.0",
        "insertText": "def calculate_total(items: List[Dict]) -> float:\n    \"\"\"Calculate total value of a shopping cart.\n\n    Given a list of items with price and quantity, returns the sum of all item values.\n\n    Example:\n\n        >>> calculate_total([\n        ...         {'price': 10.0, 'quantity': 2},\n        ...         {'price': 5.0, 'quantity': 3}\n        ...     ])\n        35.0\n    \"\"\"\n    total = 0.0\n    for item in items:\n        total += item[\"price\"] * item[\"quantity\"]\n    return total",
        "textEdit": {
          "range": {
            "start": { "line": 1, "character": 0 },
            "end": { "line": 4, "character": 12 }
          },
          "newText": "def calculate_total(items: List[Dict]) -> float:\n    \"\"\"Calculate total value of a shopping cart.\n\n    Given a list of items with price and quantity, returns the sum of all item values.\n\n    Example:\n\n        >>> calculate_total([\n        ...         {'price': 10.0, 'quantity': 2},\n        ...         {'price': 5.0, 'quantity': 3}\n        ...     ])\n        35.0\n    \"\"\"\n    total = 0.0\n    for item in items:\n        total += item[\"price\"] * item[\"quantity\"]\n    return total"
        }
      }
    ]
  },
  "id": 1
}

The developer accepts the completion. The AI then follows up with a context-aware suggestion based on recent commits and code reviews: “Add type validation for items using pydantic.” It sends a textDocument/hover request when the cursor rests on List[Dict]:

{
  "jsonrpc": "2.0",
  "method": "textDocument/hover",
  "params": {
    "textDocument": { "uri": "file:///src/cart.py" },
    "position": { "line": 1, "character": 26 }
  },
  "id": 2
}

The AI responds with a hover tooltip that includes:

This pattern works across languages: in Go, the AI suggests func ProcessOrder(items []map[string]interface{}) float64 with a go code block in the hover, and in JavaScript, it includes a TypeScript interface in the response.

Common Failure Mode: Over-Engineering

The AI often suggests too many features—adding pydantic models, logging, caching, and error handling—when only a simple function is needed. The developer must explicitly reject the AI’s suggestions via a textDocument/executeCommand request:

{
  "jsonrpc": "2.0",
  "method": "textDocument/executeCommand",
  "params": {
    "command": "codepair.reject",
    "arguments": [
      {
        "uri": "file:///src/cart.py",
        "line": 1,
        "reason": "Too detailed for initial implementation"
      }
    ]
  },
  "id": 3
}

The AI logs the rejection and updates its memory to prefer simpler outputs in this context.

Refactoring Code in Place

Refactoring a function or module is where the AI pair truly shines. The developer selects a block of code and invokes a “refactor” command, which triggers a multi-step process.

The AI agent first analyzes the selected code, then proposes a set of refactorings, each with a description, impact estimate, and a preview diff.

For example, a developer selects the body of calculate_total:

total = 0.0
for item in items:
    total += item["price"] * item["quantity"]
return total

They run :Refactor in their editor, which sends:

{
  "jsonrpc": "2.0",
  "method": "textDocument/executeCommand",
  "params": {
    "command": "codepair.refactor",
    "arguments": {
      "uri": "file:///src/cart.py",
      "range": {
        "start": { "line": 2, "character": 0 },
        "end": { "line": 5, "character": 11 }
      },
      "strategy": "extract-function",
      "context": "This code is used in multiple places; extract into a reusable helper."
    }
  },
  "id": 4
}

The AI responds with a list of possible refactorings:

{
  "jsonrpc": "2.0",
  "result": [
    {
      "name": "Extract Function: compute_item_value",
      "description": "Extract item value computation into a separate function.",
      "preview": [
        "def compute_item_value(item: Dict) -> float:\n    return item[\"price\"] * item[\"quantity\"]",
        "",
        "def calculate_total(items: List[Dict]) -> float:\n    total = 0.0\n    for item in items:\n        total += compute_item_value(item)\n    return total"
      ],
      "impact": {
        "linesChanged": 5,
        "reusability": 0.8,
        "complexity": 0.6
      },
      "appliesTo": [
        "src/cart.py",
        "src/checkout.py"
      ]
    },
    {
      "name": "Convert to List Comprehension",
      "description": "Replace loop with list comprehension.",
      "preview": [
        "def calculate_total(items: List[Dict]) -> float:\n    return sum(item[\"price\"] * item[\"quantity\"] for item in items)"
      ],
      "impact": {
        "linesChanged": 2,
        "readability": 0.9,
        "performance": 0.7
      }
    }
  ],
  "id": 4
}

The developer selects the first option. The AI applies the refactor via textDocument/applyEdit:

{
  "jsonrpc": "2.0",
  "method": "textDocument/applyEdit",
  "params": {
    "label": "Extract: compute_item_value",
    "edit": {
      "changes": {
        "file:///src/cart.py": [
          {
            "range": {
              "start": { "line": 1, "character": 0 },
              "end": { "line": 5, "character": 11 }
            },
            "newText": "def compute_item_value(item: Dict) -> float:\n    return item[\"price\"] * item[\"quantity\"]\n\n\ndef calculate_total(items: List[Dict]) -> float:\n    total = 0.0\n    for item in items:\n        total += compute_item_value(item)\n    return total"
          }
        ]
      }
    }
  },
  "id": 5
}

The AI then runs a post-refactor validation step: it checks whether the refactored code passes all unit tests, and if not, it generates a diagnostic report.

Sharp Edge: Confusion Between “Refactor” and “Improve”

The AI’s refactor command is often mistaken for improve, which focuses on code quality and style rather than structural change. The improve command runs a full code quality pass: it applies linting, adds missing imports, applies naming conventions, and suggests better abstractions.

When a developer runs :Improve, the AI sends:

{
  "jsonrpc": "2.0",
  "method": "textDocument/executeCommand",
  "params": {
    "command": "codepair.improve",
    "arguments": {
      "uri": "file:///src/cart.py",
      "rules": [
        "add-imports",
        "fix-linting",
        "add-docstrings",
        "optimize-structure"
      ]
    }
  },
  "id": 6
}

The agent then applies multiple edits in sequence, with each step visible in the editor’s diff view. The developer learns that refactor is for moving code around, while improve is for polishing it.

Reviewing Pull Requests

The AI pair becomes a permanent reviewer for pull requests. When a developer pushes a new branch, the AI scans the diff and generates a review with actionable feedback.

The review is initiated via a codepair.review command, triggered by a git push hook:

#!/bin/sh


git fetch origin
git merge origin/main

# Trigger AI review
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "branch": "feature/cart-enhancements",
    "base": "main",
    "head": "feature/cart-enhancements",
    "changes": [
      {
        "path": "src/cart.py",
        "diff": "@@ -1,6 +1,10 @@\n+def calculate_total(items: list[dict]) -> float:\n+    \"\"\"Calculate total value of a shopping cart.\n+    \"\"\"\n+    total = 0.0\n+    for item in items:\n+        total += item[\"price\"] * item[\"quantity\"]\n+    return total\n+    \n     def process_order(items: list[dict]) -> dict:\n         total = calculate_total(items)\n         return {\n             \"total\": total,\n             \"items\": items\n         }"
      }
    ]
  }' \
  http://localhost:8080/api/v1/codepair/review

The AI responds with a structured review:

{
  "id": "pr-123",
  "title": "Enhance cart calculation with type hints and better naming",
  "summary": "This PR adds a new function and improves the cart processing logic. The implementation is solid but has a few opportunities for improvement.",
  "comments": [
    {
      "path": "src/cart.py",
      "line": 1,
      "body": "Use `List[Dict]` instead of `list[dict]` for better type clarity. `List` is more explicit and aligns with the project's type conventions.",
      "severity": "suggestion"
    },
    {
      "path": "src/cart.py",
      "line": 8,
      "body": "Consider renaming `process_order` to `checkout_order` to better reflect its role in the workflow.",
      "severity": "suggestion"
    },
    {
      "path": "src/cart.py",
      "line": 1,
      "body": "Add a `@dataclass` for `CartItem` to reduce boilerplate in `process_order`.",
      "severity": "suggestion"
    },
    {
      "path": "src/cart.py",
      "line": 11,
      "body": "The `return` statement could be simplified using `dict` constructor syntax.",
      "severity": "improvement"
    }
  ],
  "suggestions": [
    "Add unit tests for `calculate_total`",
    "Document the `cart` module in `docs/cart.md`",
    "Add a `CartService` class to encapsulate cart logic"
  ],
  "status": "pending"
}

The AI pairs with the developer during code review, offering to explain a comment when the cursor hovers over a suggestion.

Failure Mode: Reviewer Fatigue

The AI generates too many comments—especially for small changes—leading to reviewer fatigue. The developer learns to filter AI comments by severity: suggestion for minor improvements, improvement for code quality, and critical for architecture-level feedback.

The AI maintains a review context across multiple pull requests. When a new PR is reviewed, it checks previous reviews and suggests that the developer “apply changes from PR #117” automatically.

Debugging in Real Time

When a developer runs a failing test, the AI pair listens to the test output and suggests fixes. It parses the error log and matches it to known failure patterns.

For example, a test fails with:

$ pytest tests/test_cart.py
============================= test session starts =============================
collected 1 item

tests/test_cart.py::test_calculate_total FAILED

============================== FAILURES ===============================
________________________ test_calculate_total _________________________

def test_calculate_total():
    items = [
        {"price": 10.0, "quantity": 2},
        {"price": 5.0, "quantity": 3}
    ]
    assert calculate_total(items) == 35.0

E   AssertionError: assert 34.99999999999999 == 35.0
E       +extraneous: 6.661338147750939e-16

=================== 1 failed, 0 passed in 0.02 seconds ===================

The AI agent parses the output and sends a textDocument/semanticHighlighting request:

{
  "jsonrpc": "2.0",
  "method": "textDocument/semanticHighlighting",
  "params": {
    "textDocument": { "uri": "file:///src/cart.py" },
    "context": {
      "range": {
        "start": { "line": 1, "character": 0 },
        "end": { "line": 5, "character": 11 }
      },
      "diagnostics": [
        {
          "range": {
            "start": { "line": 4, "character": 8 },
            "end": { "line": 4, "character": 10 }
          },
          "message": "Floating point comparison failed",
          "severity": 1,
          "source": "ai-pair"
        }
      ]
    }
  },
  "id": 7
}

The AI responds with a diagnostic explanation and a fix suggestion:

The developer accepts the fix. The AI applies it via textDocument/applyEdit:

{
  "jsonrpc": "2.0",
  "method": "textDocument/applyEdit",
  "params": {
    "label": "Fix: Use isclose for floating-point comparison",
    "edit": {
      "changes": {
        "file:///tests/test_cart.py": [
          {
            "range": {
              "start": { "line": 10, "character": 0 },
              "end": { "line": 10, "character": 10 }
            },
            "newText": "    assert math.isclose(calculate_total(items), 35.0, rel_tol=1e-9)"
          }
        ]
      }
    }
  },
  "id": 8
}

The AI then runs pytest again and reports success, closing the loop.

Sharp Edge: Silent Failure in Diagnostic Matching

The AI often fails to recognize subtle error patterns—such as KeyError when a dictionary key is missing—because it relies on a fixed set of diagnostic rules. When a test fails with:

KeyError: 'total'

The AI does not suggest that the total key is missing from the output of calculate_total. The developer learns to manually add a diagnostic.rule in the AI configuration:

ai-pair:
  diagnostics:
    - name: "missing-total-key"
      pattern: "KeyError: 'total'"
      explanation: "The function returned a dictionary missing the 'total' key."
      fix: |
        Add `result['total'] = total` after calculating total in `calculate_total`.
      priority: high

This configuration is shared across projects and maintained in the project’s config/ai-pair.yaml.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.