# Magnitude Package Layering Hierarchy Explained: Why Clients Use `client-common → sdk` Instead of Direct `acn` Imports

> Understand Magnitude's package layering hierarchy: clients -> client-common -> sdk -> acn. Learn why clients import sdk instead of directly from acn to prevent bloat and coupling.

- Repository: [Magnitude/magnitude](https://github.com/magnitudedev/magnitude)
- Tags: internals
- Published: 2026-09-06

---

**Magnitude enforces a strict four-layer package hierarchy (clients → client-common → sdk → acn) to isolate UI code from server-side daemon internals, preventing bundle bloat and runtime coupling.**

The `magnitudedev/magnitude` repository implements a meticulously designed **package layering system** that governs how code flows through its TypeScript monorepo. This architecture prevents client applications from directly importing the agent runtime (`agent`) or daemon (`acn`), instead routing all communication through a controlled chain of abstractions. Understanding this hierarchy is essential for anyone extending Magnitude's CLI, web interface, or agent capabilities.

## The Four-Layer Package Architecture

Magnitude's monorepo is organized into distinct layers with strictly enforced import boundaries. As documented in [`AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/AGENTS.md) at the repository root, each layer serves a specific purpose and exposes only what the layer above requires.

| Layer | Package | Purpose | Import Rules |
|-------|---------|---------|--------------|
| **Clients** | `@magnitudedev/cli`, `@magnitudedev/web` | UI entry points | Import only from `client-common` and `sdk` |
| **Client-Common** | `@magnitudedev/client-common` | Shared state, hooks, display sync | Owns Query/Mutation/Subscription definitions over SDK methods |
| **SDK** | `@magnitudedev/sdk` | Portable RPC client | Re-exports pure protocol contracts from `acn-protocol`; contains no SQLite or process logic |
| **ACN** (daemon) | `@magnitudedev/acn` | Server-side agent runtime | Implements sessions, file operations, display streams; imported only by privileged composition roots |

This structure is not merely conventional—it is mechanically enforced through the codebase's module boundaries and build configuration.

## Why Clients Must Import Through `client-common` and `sdk`

Direct imports from `acn` or `agent` into client code violate Magnitude's architectural contract. The [`AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/AGENTS.md) specification explicitly prohibits this pattern for three critical reasons.

### Dependency Isolation

The `acn` package contains **heavy runtime dependencies** including SQLite, process supervision, and OS-service code. The `agent` package implements the actual runtime that executes tool calls and manages browser automation. Pulling either into a client bundle would:

- Dramatically increase bundle size with server-only dependencies
- Make the client unusable in browser environments where these native dependencies cannot execute
- Tightly couple UI code to a specific host runtime

The SDK layer solves this by providing a **pure, portable RPC client** that knows only about wire contracts, not implementation details.

### Stability Through Protocol Contracts

The `sdk` re-exports types from `@magnitudedev/acn-protocol`—a **stable, versioned contract** separate from the daemon's internal implementation. This ensures that:

- Client code depends only on protocol guarantees, not implementation details
- Version checks and replay policies (`replaySafe`, `atMostOnce`) are enforced at the SDK boundary
- Daemon upgrades don't break existing clients as long as protocol compatibility is maintained

### Centralized State Management

The `client-common` layer owns all **first-party Effect Query/Mutation/Subscription definitions**. This centralization provides:

- A single source of truth for UI state derived from agent operations
- Consistent error handling and loading states across CLI and web interfaces
- Clean separation between connection-scoped concerns and presentation logic

## Correct vs. Incorrect Import Patterns

The codebase demonstrates the proper layering through concrete examples in [`packages/client-common/src/operations/agents.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/client-common/src/operations/agents.ts) and [`packages/sdk/src/index.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/sdk/src/index.ts).

### Correct: Client-Layer Import

```typescript
// CLI or web client code
import { useAgentClient } from '@magnitudedev/client-common';
import { listAgents } from '@magnitudedev/sdk';

// Hook operates over the SDK without knowing daemon internals
const { data: agents } = useAgentClient(listAgents);

```

This maintains the layering boundary: the client knows only about React hooks and SDK methods.

### Correct: Client-Common Query Definition

```typescript
// packages/client-common/src/operations/agents.ts
import { Rpc } from '@magnitudedev/sdk';
import { AgentQuery } from '@magnitudedev/acn-protocol';

// High-level query that UI can consume
// Encapsulates SDK interaction in a reusable Effect Query
export const fetchAgents = Rpc.make(AgentQuery.getAll);

```

`client-common` bridges the gap: it uses SDK primitives (`Rpc.make`) with protocol contracts (`AgentQuery`) to build UI-friendly abstractions.

### Correct: SDK RPC Wrapper

```typescript
// packages/sdk/src/index.ts
import { AgentQuery } from '@magnitudedev/acn-protocol';
import { rpc } from '@magnitudedev/sdk/internal';

// Pure function over protocol contract—no daemon implementation leaked
export const listAgents = () => rpc(AgentQuery.list);

```

The SDK remains agnostic to how `AgentQuery.list` is ultimately implemented by the daemon.

### Incorrect: Direct Daemon Import

```typescript
// ❌ NEVER import directly in client code
import { startSession } from '@magnitudedev/acn';
import { ToolRuntime } from '@magnitudedev/agent';

// This breaks layering, bloats bundle, and creates runtime coupling

```

These imports are reserved exclusively for **privileged composition roots**: the CLI bootstrap, desktop main process, and development server.

## Where Layering Is Enforced in Source

The architectural rules are documented and implemented across several key files in the repository:

- **[`AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/AGENTS.md)** – root-level specification defining the full package hierarchy and import rules
- **[`packages/client-common/AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/packages/client-common/AGENTS.md)** – details Query/Mutation patterns over SDK methods
- **[`packages/sdk/AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/packages/sdk/AGENTS.md)** – explains the private RPC client and protocol re-export strategy
- **[`packages/acn-protocol/AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn-protocol/AGENTS.md)** – documents the wire contracts shared between SDK and daemon

These files collectively serve as the source of truth for maintaining boundary discipline as the codebase evolves.

## Summary

Magnitude's **package layering hierarchy** (clients → client-common → sdk → acn) enforces strict separation of concerns across its monorepo:

- **Clients** import only from `client-common` and `sdk` to remain lightweight and environment-agnostic
- **Client-common** centralizes state management through Effect Queries that wrap SDK methods
- **SDK** provides a portable, pure RPC layer that re-exports stable protocol contracts
- **ACN** implements server-side behavior accessible only to privileged host composition roots

This design prevents bundle bloat, maintains protocol stability, and allows each layer to evolve independently as long as contract compatibility is preserved.

## Frequently Asked Questions

### What happens if I import directly from `acn` in a web client?

Direct imports from `@magnitudedev/acn` in browser-facing code will cause build failures or runtime errors. The `acn` package contains Node.js-specific dependencies (SQLite native bindings, process supervisors, OS service integrations) that cannot execute in browser environments. Even if tree-shaking removes some dead code, the bundler will likely fail to resolve these platform-specific modules.

### Why does `sdk` re-export from `acn-protocol` instead of `acn`?

The `acn-protocol` package contains **pure TypeScript contracts**—interfaces, type definitions, and Effect schemas that describe the wire format. The `acn` package contains the **actual implementation**—servers, session managers, and runtime logic. By re-exporting from `acn-protocol`, the SDK maintains zero runtime dependency on server-specific code while still speaking the same language on the wire.

### Can I extend Magnitude by adding a new layer between `sdk` and `acn`?

Adding intermediate layers is possible but requires careful consideration of the **composition root** pattern. The [`AGENTS.md`](https://github.com/magnitudedev/magnitude/blob/main/AGENTS.md) documentation suggests that only privileged bootstrap code should import `acn` directly. Any new intermediate layer would need to either become part of the SDK's public API (breaking the encapsulation) or remain server-side (leaving the SDK boundary unchanged). Most extensions should instead add functionality within the existing `acn` implementation or expose new protocol contracts through `acn-protocol`.