# Understanding the Three-Layer Architecture of the WorkWeave Router

> Explore the WorkWeave Router's three-layer architecture: Presentation, Business Logic, and Infrastructure. Discover how this design ensures clean code and eliminates circular dependencies.

- Repository: [Weave/router](https://github.com/workweave/router)
- Tags: architecture
- Published: 2026-08-30

---

**The WorkWeave Router implements a strict concentric three-layer architecture comprising a Presentation/Composition Root layer for HTTP handling and dependency injection, a Business Logic/Inner-Ring layer containing pure Go domain logic without I/O, and an Infrastructure/Leaf layer for concrete adapters like PostgreSQL and upstream providers, enforced by inward-only import rules that eliminate circular dependencies.**

The `workweave/router` repository organizes its codebase around a disciplined three-layer architecture documented in **AGENTS.md**. This structural pattern separates presentation concerns from domain logic and infrastructure dependencies, ensuring that the core routing engine remains agnostic of external implementations while maintaining strict boundaries between layers.

## The Three Layers Explained

The architecture consists of three concentric layers where dependencies always point inward. Outer layers may depend on inner layers, but inner layers remain completely isolated from external concerns.

### Layer 1: Presentation and Composition Root

The outermost layer handles HTTP concerns and serves as the application's **composition root**. Primary packages include [`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go), `internal/api/*`, and `internal/server`. This layer is responsible for HTTP routing, request parsing, middleware application, and—critically—the instantiation and injection of all concrete implementations.

According to the source code, [`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go) is the sole location where adapters like database pools and provider clients are created and wired into business logic services. This ensures that inner-ring packages remain pure and free of instantiation logic.

### Layer 2: Business Logic (Inner Ring)

The middle layer contains the core domain behavior and is strictly prohibited from performing I/O operations. Key packages include `internal/auth` (API-key verification), `internal/billing` (balance checks and inference debiting), `internal/proxy` (routing and dispatch), and `internal/router/*` (containing the router interface, request/decision types, planners, cache, and catalog).

Packages like `internal/feedback`, `internal/analytics`, and `internal/websearch` also reside here. These components expose interfaces that the outer layer implements, but they contain only pure functions and value types. For example, the planner logic in `internal/router/planner` operates entirely on in-memory data structures without network or filesystem calls.

### Layer 3: Infrastructure and Leaf Utilities

The innermost layer provides concrete implementations for persistence, external APIs, and observability. Primary packages include `internal/config` (environment helpers), `internal/observability` (logging and OpenTelemetry), `internal/postgres` (SQLC-generated adapters), and `internal/providers/*` (upstream provider clients like OpenAI).

These adapters may import inner-ring packages to report metrics or timestamps, but the dependency graph strictly prevents inner-ring packages from importing infrastructure code. This guarantees that business logic never accidentally couples to specific database implementations or external service clients.

## How the Layers Interact

### Strict Import Directionality

The architecture enforces a hard rule: packages may only import *inward*. For example, `internal/api/admin` can import `internal/auth` and `internal/router`, but it cannot import `internal/postgres` directly. This constraint eliminates circular dependencies and forces developers to route all persistence operations through the domain layer's interfaces.

### The Composition Root Pattern

In [`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go), the application assembles all concrete adapters and injects them into inner-ring services. This is the only location where constructors like `postgres.NewRepo`, `auth.NewService`, and `proxy.NewService` are called with their concrete dependencies. By centralizing wiring logic, the codebase keeps domain services agnostic of whether they use PostgreSQL or another storage backend.

### Pure Functions in the Inner Ring

Inner-ring packages like `internal/router/planner` implement logic using only pure functions that transform data structures. The `Planner.Plan` method, for instance, accepts a `router.Request` and returns a `router.Decision` based solely on in-memory catalog lookups and policy checks. This "no I/O" rule guarantees that unit tests can verify routing logic without mocking external services or database connections.

## Architectural Patterns in Code

The following examples demonstrate how the three-layer architecture manifests in the `workweave/router` source code.

### Wiring Dependencies in the Composition Root

The [`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go) file illustrates how concrete implementations are instantiated and injected into inner-ring services:

```go
// Create the concrete PostgreSQL repo.
pgRepo := postgres.NewRepo(pgPool)

// Instantiate the auth service (inner‑ring) with the repo and logger.
authSvc := auth.NewService(pgRepo, logger)

// Build the proxy service (inner‑ring) that depends on auth, billing, and providers.
proxySvc := proxy.NewService(
    authSvc,
    billing.NewService(pgRepo, logger),
    providers.NewOpenAIClient(logger), // infrastructure adapter
    logger,
)

// Register HTTP handlers (presentation layer) and inject the proxy.
api.RegisterRoutes(router, proxySvc)

```

This snippet shows the outer layer creating concrete adapters (`postgres.NewRepo`, `providers.NewOpenAIClient`) and passing them to inner-ring services (`auth.NewService`, `proxy.NewService`).

### Pure Business Logic Without I/O

The [`internal/router/planner/planner.go`](https://github.com/workweave/router/blob/main/internal/router/planner/planner.go) file demonstrates the inner-ring's purity:

```go
// Plan decides which model to use for a request, using only pure data.
func (p *Planner) Plan(req router.Request) (router.Decision, error) {
    // Look up model pricing from the catalog (no I/O).
    modelInfo := p.Catalog.Lookup(req.Model)

    // Apply policy (e.g., cost ceiling) and return a Decision.
    if req.Budget < modelInfo.Cost {
        return router.Decision{}, fmt.Errorf("budget too low")
    }
    return router.Decision{Model: modelInfo.ID, Provider: modelInfo.Provider}, nil
}

```

The planner works entirely with in-memory data, illustrating the strict "no I/O" rule that enables straightforward unit testing.

### Infrastructure Adapter Implementation

The [`internal/providers/openai/client.go`](https://github.com/workweave/router/blob/main/internal/providers/openai/client.go) file shows how infrastructure interacts with the inner ring:

```go
func (c *Client) ChatCompletion(ctx context.Context, req OpenAIRequest) (OpenAIResponse, error) {
    // Perform the HTTP request (infrastructure layer).
    resp, err := c.http.Do(req.WithContext(ctx))
    if err != nil {
        return OpenAIResponse{}, err
    }
    // Record timing via the inner‑ring timing package.
    timing.Record(ctx, "openai.request", time.Since(start))
    return parseResponse(resp)
}

```

This adapter performs external HTTP calls while depending on the inner-ring `timing` package for observability, respecting the inward-only import rule.

## Key Files Demonstrating the Architecture

Several critical files illustrate how the three-layer architecture operates in practice:

- **[`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go)**: The composition root where all adapters are instantiated and injected.
- **[`internal/api/admin/health.go`](https://github.com/workweave/router/blob/main/internal/api/admin/health.go)**: Presentation layer endpoint that delegates to inner-ring health checks.
- **[`internal/router/router.go`](https://github.com/workweave/router/blob/main/internal/router/router.go)**: Defines the `Router` interface and core request/decision types in the business logic layer.
- **[`internal/proxy/service.go`](https://github.com/workweave/router/blob/main/internal/proxy/service.go)**: Orchestrates authentication, billing, and provider calls as a central inner-ring service.
- **[`internal/postgres/repo.go`](https://github.com/workweave/router/blob/main/internal/postgres/repo.go)**: Concrete PostgreSQL adapter implementing repositories used by inner-ring services.
- **[`internal/providers/openai/client.go`](https://github.com/workweave/router/blob/main/internal/providers/openai/client.go)**: Infrastructure adapter performing external I/O and reporting metrics.
- **[`internal/config/config.go`](https://github.com/workweave/router/blob/main/internal/config/config.go)**: Leaf utility providing environment-variable helpers without importing other internal packages.

## Summary

- The **three-layer architecture** of the WorkWeave Router separates concerns into Presentation, Business Logic, and Infrastructure layers.
- **Import direction** strictly flows inward, preventing circular dependencies and keeping domain logic isolated.
- The **composition root** at [`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go) is the only location where concrete implementations are instantiated and wired together.
- The **inner ring** prohibits I/O operations, ensuring pure business logic that is easily testable without external mocks.
- **Infrastructure adapters** depend on inner-ring packages for observability but never vice versa, enabling swappable implementations.

## Frequently Asked Questions

### What defines the three-layer architecture in the WorkWeave Router?

The architecture consists of a Presentation layer ([`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go), `internal/api`), a Business Logic layer (`internal/router`, `internal/proxy`, `internal/auth`), and an Infrastructure layer (`internal/postgres`, `internal/providers`). Dependencies may only point inward, ensuring that business logic remains decoupled from HTTP handling and database implementations.

### Why does the inner ring prohibit I/O operations?

The inner ring restricts I/O to guarantee that domain logic consists of pure functions operating on in-memory data structures. This design eliminates side effects during unit testing and ensures that routing decisions, billing calculations, and authentication checks remain deterministic and fast without requiring external service mocks.

### How does the composition root pattern work in this codebase?

The composition root located at [`cmd/router/main.go`](https://github.com/workweave/router/blob/main/cmd/router/main.go) instantiates all concrete adapters—such as `postgres.NewRepo` and `providers.NewOpenAIClient`—and injects them into inner-ring services like `auth.NewService` and `proxy.NewService`. This singleton wiring location keeps the remainder of the codebase agnostic of concrete implementation details.

### Can infrastructure packages import business logic packages?

Yes. Infrastructure packages at the outer layer may import inner-ring packages to utilize types, interfaces, or observability utilities like `internal/timing`. However, the reverse is strictly prohibited: business logic packages cannot import infrastructure code, maintaining the architectural boundary.