# Source Control Provider Abstraction Architecture in Background Agents

> Explore the source control provider abstraction architecture in background agents. This pluggable interface isolates SCM operations for GitHub, GitLab, and Bitbucket via a single contract.

- Repository: [Cole Murray/background-agents](https://github.com/ColeMurray/background-agents)
- Tags: architecture
- Published: 2026-07-13

---

**The source control provider abstraction in Background Agents is a pluggable interface layer that isolates all SCM operations behind a unified factory pattern, supporting GitHub, GitLab, and Bitbucket through a single type-safe contract.**

The ColeMurray/background-agents repository implements a robust control-plane package that decouples source-control operations from specific provider implementations. This architecture enables the system to interact with multiple Git hosting platforms through a consistent API surface while maintaining type safety and centralized error handling.

## Core Architecture Components

The abstraction follows a classic "interface + factory + concrete implementations" pattern located in `packages/control-plane/src/source-control/`. This design ensures that consumer code remains provider-agnostic while supporting runtime selection via environment configuration.

### Type Definitions and Contracts

All shared types and interfaces reside in [`packages/control-plane/src/source-control/types.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/types.ts). This file defines the uniform contract that every provider must satisfy, including:

- **`RepositoryInfo`**: Metadata structure for repository data
- **`SourceControlAuthContext`**: Authentication context interface
- **`GitPushSpec`**: Specification for git push operations
- **`CreatePullRequestConfig`**: Configuration type for PR creation
- **`SourceControlProviderName`**: Union type defining supported providers (`"github" | "bitbucket" | "gitlab"`)

These TypeScript definitions ensure consistent typing across the entire source control provider abstraction, preventing provider-specific data structures from leaking into consumer code.

### Provider Interface

While the interface definition sits alongside the types file, concrete implementations must satisfy methods such as:

- `getRepository()`: Fetch repository metadata
- `createPullRequest()`: Generate new pull requests
- `buildGitPushSpec()`: Construct git push specifications
- `getCredentialHelperAuth()`: Retrieve authentication credentials
- `listRepositories()`: Enumerate accessible repositories

This interface acts as the runtime boundary that all concrete providers must implement.

### Factory Pattern Implementation

The factory located in [`packages/control-plane/src/source-control/providers/index.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/providers/index.ts) handles provider instantiation. It reads the `NEXT_PUBLIC_SCM_PROVIDER` environment variable at runtime and returns the appropriate concrete class instance:

```typescript
// Factory logic from providers/index.ts
switch (providerName) {
  case "github":
    return new GitHubProvider(config);
  case "gitlab":
    return new GitLabProvider(config);
  case "bitbucket":
    return new BitbucketProvider(config);
  default:
    throw new ProviderNotSupportedError(providerName);
}

```

The factory throws `Unsupported source control provider` for unknown provider names, ensuring fail-fast behavior on misconfiguration.

### Concrete Provider Implementations

Each supported platform lives in its own module within the providers directory:

- **GitHub**: [`packages/control-plane/src/source-control/providers/github-provider.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/providers/github-provider.ts) implements the interface using GitHub's REST and GraphQL APIs
- **GitLab**: [`packages/control-plane/src/source-control/providers/gitlab-provider.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/providers/gitlab-provider.ts) provides the same contract against GitLab's API
- **Bitbucket**: [`packages/control-plane/src/source-control/providers/bitbucket-provider.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/providers/bitbucket-provider.ts) follows the identical pattern for Bitbucket Cloud

These classes encapsulate platform-specific API logic while exposing the standard interface to the rest of the system.

## Error Handling Strategy

Centralized error types in [`packages/control-plane/src/source-control/errors.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/errors.ts) provide consistent exception handling across providers. Key error classes include:

- **`SourceControlError`**: Base error for all SCM operations
- **`ProviderNotSupportedError`**: Thrown when the factory encounters an unregistered provider name

This centralization ensures that consumers can catch and handle source-control failures uniformly without importing provider-specific error types.

## Consumer Usage Pattern

The control-plane accesses the abstraction lazily through session-based helpers. In [`packages/control-plane/src/session/durable-object.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/durable-object.ts), the `getSourceControlProvider()` function creates providers on-demand and caches instances for the session duration.

Routes such as [`src/routes/repos.ts`](https://github.com/ColeMurray/background-agents/blob/main/src/routes/repos.ts) and [`src/routes/scm-credentials.ts`](https://github.com/ColeMurray/background-agents/blob/main/src/routes/scm-credentials.ts) demonstrate the consumer pattern:

```typescript
import { getSourceControlProvider } from "./session/durable-object";

async function listRepos(sessionId: string) {
  const provider = await getSourceControlProvider(sessionId);
  const repos = await provider.listRepositories();
  return repos;
}

```

This approach ensures that routes work against the generic interface without branching on provider names, maintaining clean separation of concerns.

## Extensibility: Adding a New Provider

Extending the source control provider abstraction requires three steps:

1. **Extend the union type** in [`types.ts`](https://github.com/ColeMurray/background-agents/blob/main/types.ts):

   ```typescript
   export type SourceControlProviderName = "github" | "gitlab" | "bitbucket" | "mynewprovider";
   ```

2. **Create the implementation** in a new file (e.g., [`mynewprovider.ts`](https://github.com/ColeMurray/background-agents/blob/main/mynewprovider.ts)) satisfying the provider interface

3. **Register in the factory** in [`providers/index.ts`](https://github.com/ColeMurray/background-agents/blob/main/providers/index.ts):

   ```typescript
   case "mynewprovider":
     return new MyNewProvider(config);
   ```

This architecture guarantees that adding a new SCM platform only requires changes to the provider layer, leaving the rest of the codebase untouched.

## Summary

- **Pluggable Architecture**: The source control provider abstraction uses a factory pattern to instantiate GitHub, GitLab, or Bitbucket providers based on the `NEXT_PUBLIC_SCM_PROVIDER` environment variable
- **Type Safety**: Centralized type definitions in [`types.ts`](https://github.com/ColeMurray/background-agents/blob/main/types.ts) ensure consistent contracts across all providers
- **Lazy Initialization**: `getSourceControlProvider()` in [`durable-object.ts`](https://github.com/ColeMurray/background-agents/blob/main/durable-object.ts) caches provider instances per session
- **Centralized Errors**: [`errors.ts`](https://github.com/ColeMurray/background-agents/blob/main/errors.ts) defines uniform exception handling for all SCM operations
- **Easy Extension**: New providers require only interface implementation and factory registration without modifying consumer code

## Frequently Asked Questions

### How does the factory determine which provider to instantiate?

The factory reads the `NEXT_PUBLIC_SCM_PROVIDER` environment variable at build time or the runtime `runtimeProvider` variable, then matches this against the `SourceControlProviderName` union type. It returns an instance of `GitHubProvider`, `GitLabProvider`, or `BitbucketProvider` based on the configured value, throwing `ProviderNotSupportedError` for unknown names.

### What methods must a new source control provider implement?

Any concrete provider must implement the interface defined alongside [`types.ts`](https://github.com/ColeMurray/background-agents/blob/main/types.ts), including `getRepository()`, `createPullRequest()`, `buildGitPushSpec()`, `getCredentialHelperAuth()`, and `listRepositories()`. These methods ensure that consumers can perform all necessary SCM operations without knowing the underlying platform.

### Where does error handling for SCM operations occur?

Error handling is centralized in [`packages/control-plane/src/source-control/errors.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/errors.ts), which defines `SourceControlError` and `ProviderNotSupportedError`. This allows routes like [`repos.ts`](https://github.com/ColeMurray/background-agents/blob/main/repos.ts) and [`scm-credentials.ts`](https://github.com/ColeMurray/background-agents/blob/main/scm-credentials.ts) to catch and handle failures uniformly, regardless of which concrete provider generated the error.

### Can consumer code access provider-specific details directly?

No. Consumer code in routes such as [`src/routes/repos.ts`](https://github.com/ColeMurray/background-agents/blob/main/src/routes/repos.ts) and [`src/routes/scm-credentials.ts`](https://github.com/ColeMurray/background-agents/blob/main/src/routes/scm-credentials.ts) accesses the abstraction through `getSourceControlProvider()`, which returns the generic interface. This design prevents provider-specific logic from leaking into the application layer and maintains strict separation between the control-plane and external SCM APIs.