# How Magnitude's Two-Layer Daemon Architecture (ACN and ICN) Works

> Discover how Magnitude's two-layer daemon architecture, ACN and ICN, separates local runtime to manage durable application state and host inference engines for efficient local model execution.

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

---

**Magnitude separates its local runtime into two cooperating daemons: the Application Control Daemon (ACN) manages durable application state and owns the Inference Control Daemon (ICN), which hosts the actual local-model inference engine.**

The **ACN** and **ICN** form a parent-child relationship that isolates application lifecycle concerns from ML inference workloads. According to the Magnitude source code, this design lets clients interact with a single stable RPC surface while the ACN handles process supervision, recovery, and sandbox isolation of the ICN child. The architecture is implemented across `packages/acn`, `packages/icn-protocol`, and `packages/sdk` in the [magnitudedev/magnitude](https://github.com/magnitudedev/magnitude) repository.

---

## ACN Responsibilities: The Application Control Daemon

The **ACN** is the authoritative local daemon that owns all durable application state.

- **Process lifecycle management**: Handles ACN spawning, startup grace periods (2 s publication grace), shutdown sequences, and recovery logic.
- **Fenced spawn claims**: Enforces that only one client may publish a new ACN candidate at a time, preventing race conditions without a separate coordinator.
- **ICN ownership**: Launches, supervises, and terminates the ICN child process when shutting down.

The lifecycle rules are documented in [[`info/acn/lifecycle.md`](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md)](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md) and implemented in [[`packages/acn/src/boundary/acn.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/boundary/acn.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/boundary/acn.ts).

---

## ICN Responsibilities: The Inference Control Daemon

The **ICN** hosts the actual local-model inference engine.

- **Inference backends**: Supports GGUF, Llama.cpp, and mlx-LM model formats.
- **Model catalog**: Exposes discovery endpoints for available models.
- **Generation endpoints**: Handles chat completions and other inference tasks.

Clients never talk directly to the ICN. Instead, the ACN forwards requests using the **ICN protocol** (`@magnitudedev/icn-protocol`), as shown in [[`packages/acn/src/icn-command-failure.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/icn-command-failure.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/icn-command-failure.ts).

---

## How the Two Layers Interact

### JIT Ensurance of the ACN

When a client initializes, the SDK calls `ensureACN(target)` to guarantee a compatible ACN is running. This function:

1. Scans the filesystem for an existing ACN binary.
2. If absent, spawns a new ACN process.
3. Blocks until the ACN reaches `Ready` state (respecting the 30 s startup stall window and 5 min absolute ceiling).

```typescript
import { ensureACN } from "@magnitudedev/sdk"

// Blocks until ACN is Ready; handles spawn claims automatically
await ensureACN({ target: "default" })

```

The lifecycle constraints are defined in [[`info/acn/lifecycle.md`](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md)](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md).

### ACN Spawning and Claim Coordination

Magnitude uses **fenced spawn claims** to coordinate without a central coordinator:

- One client holds the spawn claim exclusively.
- That client publishes a new ACN candidate.
- Other clients observe the claim and either adopt the new ACN or wait for `Ready` state.

This avoids split-brain scenarios during ACN replacement.

### ACN Starts and Monitors ICN

Once `Ready`, the ACN launches its ICN child:

- Keeps a process handle for monitoring and termination.
- Wraps ICN errors into `LocalModelMutationFailed` via `icnCommandFailure()`.
- Enforces sandbox isolation boundaries.

Error translation occurs in [[`packages/acn/src/icn-command-failure.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/icn-command-failure.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/icn-command-failure.ts):

```typescript
import { icnCommandFailure } from "@magnitudedev/acn"

// Convert low-level transport errors to typed failures
const err = icnCommandFailure("chatCompletion", transportError)

```

### The Client → ACN → ICN RPC Flow

| Step | Actor | Action |
|------|-------|--------|
| 1 | Client | Calls SDK method (e.g., `listModels`, `createChatCompletion`). |
| 2 | SDK | Routes request to ACN RPC surface. |
| 3 | ACN | Forwards to ICN using ICN protocol. |
| 4 | ICN | Executes inference, returns result. |
| 5 | ACN | Adds metadata (model ranking, fits) and returns to client. |

The ICN protocol schemas live in [`packages/icn-protocol/schemas/`](https://github.com/magnitudedev/magnitude/tree/main/packages/icn-protocol/schemas).

### Recovery and Replacement

If the ACN transport fails, the SDK **does not** assume the ACN is dead. Instead:

- Re-observes ACN state via filesystem signals.
- Spawns a compatible successor ACN if needed.
- Relaunches ICN under the new ACN parent.

This "recovery without panic" behavior is documented in [[`info/acn/lifecycle.md`](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md)](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md).

---

## Complete Usage Example

Below is a runnable pattern showing how a client performs inference through the two-layer architecture:

```typescript
// 1️⃣ Ensure ACN is running (JIT ensurance with lifecycle handling)
import { ensureACN } from "@magnitudedev/sdk"
await ensureACN({ target: "default" })

// 2️⃣ Create ICN client (SDK hides ACN↔ICN routing)
import { makeIcnApiClient } from "@magnitudedev/icn-protocol/client"
import * as S from "@magnitudedev/icn-protocol/schemas"

const icn = makeIcnApiClient({
  baseUrl: "http://127.0.0.1:8000"  // Routed to ACN-managed ICN
})

// 3️⃣ Perform OpenAI-compatible chat completion
const response = await icn.chat.completions.create({
  model: "ggml-phi-2",
  messages: [{ role: "user", content: "Explain ACN vs ICN" }]
})

// 4️⃣ Handle ICN errors (translated by ACN layer)
if (response._tag === "GeneratedClientTransportError") {
  const err = icnCommandFailure("chatCompletion", response)
  console.error(err.message)
}

```

Key implementation notes from the source:

- `ensureACN()` encapsulates spawn claims, contention handling, and grace periods.
- `makeIcnApiClient()` builds on the SDK's [[`inference-client.ts`](https://github.com/magnitudedev/magnitude/blob/main/inference-client.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/sdk/src/inference-client.ts).
- Errors are uniformly typed as `LocalModelMutationFailed` for consistent retry policies.

---

## Key Source Files

| Component | File | Purpose |
|-----------|------|---------|
| **ACN Core** | [[`packages/acn/src/boundary/acn.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/boundary/acn.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/boundary/acn.ts) | RPC surface and lifecycle state machine. |
| **ACN Protocol** | [[`packages/acn-protocol/src/boundary/acn.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn-protocol/src/boundary/acn.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/acn-protocol/src/boundary/acn.ts) | Wire contract for SDK↔ACN communication. |
| **ICN Error Mapping** | [[`packages/acn/src/icn-command-failure.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/icn-command-failure.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/acn/src/icn-command-failure.ts) | ICN error → `LocalModelMutationFailed` conversion. |
| **SDK Inference API** | [[`packages/sdk/src/inference-client.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/sdk/src/inference-client.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/sdk/src/inference-client.ts) | High-level client for model operations. |
| **Lifecycle Rules** | [[`info/acn/lifecycle.md`](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md)](https://github.com/magnitudedev/magnitude/blob/main/info/acn/lifecycle.md) | Startup, shutdown, and recovery semantics. |
| **ICN Schemas** | [`packages/icn-protocol/schemas/`](https://github.com/magnitudedev/magnitude/tree/main/packages/icn-protocol/schemas) | Model catalog and completion request/response types. |

---

## Summary

- **ACN** owns durable state, process lifecycle, and ICN supervision. It provides the sole RPC entry point for clients.
- **ICN** runs isolated inference engines (GGUF, Llama.cpp, mlx-LM) and exposes model operations via the ICN protocol.
- **Coordination** uses JIT ensurance (`ensureACN`), fenced spawn claims, and filesystem-based state observation—no separate coordinator process required.
- **Recovery** is stateless: failed transports trigger re-observation, not panic, allowing seamless ACN/ICN replacement.

---

## Frequently Asked Questions

### What happens if the ICN crashes while the ACN is running?

The ACN monitors the ICN process handle and will terminate the ICN on shutdown. If the ICN exits unexpectedly, the ACN can relaunch it or propagate the failure via `icnCommandFailure()` as a `LocalModelMutationFailed` error, allowing clients to retry through a fresh ACN instance.

### Why does Magnitude use two daemons instead of one?

Separation of concerns: the ACN handles long-lived application state and stable client-facing RPC, while the ICN runs sandboxed inference workloads that may crash, hang, or consume GPU resources unpredictably. This isolation prevents model-engine failures from corrupting application state.

### How does `ensureACN()` prevent multiple clients from spawning conflicting ACNs?

It uses **fenced spawn claims**—filesystem-based exclusive locks where only one client holds the claim at a time. That client publishes the new ACN candidate; others observe and either adopt it or wait for `Ready` state, eliminating race conditions without a centralized service.

### Can clients bypass the ACN and talk directly to the ICN?

No. The ICN protocol is only exposed through the ACN's forwarding layer. Clients use `makeIcnApiClient()` from the SDK, which routes through the ACN RPC surface. The ICN's network port is not directly exposed to clients, enforcing the ACN's supervision and sandbox policies.