# Understanding the Epoch Mechanism in Cordis Fibers: How It Enables Plugin Composability

> Discover the epoch mechanism in Cordis Fibers. This system tracks effects, aborts stale work, and restarts tasks on dependency changes for composable plugin architectures. Learn how it works.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: deep-dive
- Published: 2026-08-24

---

**The epoch mechanism in Cordis Fibers is a lightweight generation-tracking system that assigns every active effect a unique identifier (`string | boolean`), automatically aborting stale asynchronous work and restarting effects when dependencies change, which enables safe, composable plugin architectures.**

The `cordiverse/cordis` framework models each plugin as a lightweight execution unit called a fiber that can be started, stopped, and reloaded independently. Understanding the **epoch mechanism in Cordis Fibers** is essential for building reactive applications that manage complex dependency trees without race conditions or memory leaks. This mechanism operates through the `EffectRunner` class and provides deterministic lifecycle management for async effects.

## What Is the Epoch Mechanism in Cordis Fibers?

At its core, the epoch mechanism functions as a version-tracking system for asynchronous operations. It provides a way to distinguish between different generations of a fiber's active effects, ensuring that only the current generation continues executing while previous iterations are safely discarded.

### The Epoch as a Generation Identifier

An epoch is a simple identifier with the type `string | boolean` that is stored on the fiber's internal `EffectRunner`. According to the source code in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts), the `epoch` property is declared at lines 72-76 within the `EffectRunner` class. This property records the current generation of the fiber's active effect, allowing the system to detect when a fiber transitions between active and inactive states or when its dependencies change.

When the epoch value changes, any previously started asynchronous work is considered obsolete. The previous generation's operations receive a signal to stop early, preventing them from interfering with the new execution context.

### The INACTIVE State

The mechanism relies on a special constant called `INACTIVE` (defined at lines 146-148 in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts)). This constant represents the epoch value assigned to fibers that are not currently running. When a fiber is first created or when it is fully stopped, its runner initializes with `INACTIVE` as the epoch value, effectively marking it as dormant until explicitly activated.

## How Epochs Drive Fiber Lifecycle Transitions

The transition between fiber states—whether starting fresh, reloading due to dependency changes, or shutting down—is governed by epoch manipulation. The system uses specific methods to compute and assign these values, triggering appropriate lifecycle hooks based on the transition type.

### Initializing and Updating Epochs via `_setEpoch`

The `_setEpoch` method in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts) (lines 386-396) computes the appropriate epoch value for a fiber based on its current state and dependencies. This method evaluates three conditions:

- If the fiber is starting cleanly, it assigns an empty string `""`
- If the fiber has dependencies, it generates a colon-separated list of dependent fiber IDs
- If a required dependency is missing, it assigns the `INACTIVE` constant

After computation, lines 399-402 assign this value to `runner.epoch`. The method then examines the transition at lines 405-411, triggering `_reload` if moving from `INACTIVE` to an active state, or `_unload` if transitioning to `INACTIVE`.

### Epoch-Aware Async Execution

The central `_execute` method (lines 263-267) implements the actual safety check that makes epochs useful. Before invoking an effect, `_execute` captures the current epoch value as `oldEpoch`. Throughout the async iteration of the effect—whether using generators or promises—the runner repeatedly verifies that `runner.epoch !== oldEpoch`. If the epoch has changed during execution, the iteration aborts immediately.

This check guarantees that once a fiber's epoch updates (for example, because a dependency changed or `restart()` was called), any in-flight async work from the previous epoch is safely discarded before the new effect begins.

## How the Epoch Mechanism Enables Composability

The epoch system directly supports Cordis's composable architecture by allowing fibers to depend on one another while maintaining isolated execution contexts. This prevents the "spaghetti state" common in plugin systems where cascading changes create unpredictable side effects.

### Dependency Injection and Epoch Propagation

Fibers declare dependencies through the `inject` map in their configuration. When a dependency's epoch changes, the dependent fiber automatically receives a new epoch via the `_refresh` method. This propagation causes the dependent fiber's async effects to restart, picking up the new state from its dependencies.

Because the epoch change triggers a complete teardown of the old effect (via the `_execute` abort check) before starting the new one, the system ensures that fibers never operate with stale data from previous configurations.

### Isolated Effect Lifecycles

Each effect executes within a scope tied to a distinct epoch identifier. This isolation means that nested or parallel effects within the same fiber do not interfere with each other—their lifecycles are cleanly separated by the epoch boundary. When composing multiple fibers, each reacts to configuration changes independently, and the epoch mechanism guarantees that these reactions occur in a deterministic order without race conditions.

## Practical Implementation: Epoch-Aware Plugins

Below is a practical example demonstrating how fibers use epochs to manage long-running async operations. The `ticker` plugin runs a periodic loop that automatically stops when its epoch changes, while the `controller` plugin demonstrates how dependencies trigger epoch refreshes.

```typescript
import { createApp } from '@cordis/create'

// Simple plugin that logs a message every second
export const ticker = {
  name: 'ticker',
  inject: {},
  async callback(ctx) {
    // Effect runs a periodic async loop scoped to the current epoch
    ctx.fiber.effect(async function* () {
      while (true) {
        await new Promise(r => setTimeout(r, 1000))
        ctx.logger.info('tick')
        // The loop stops automatically if the fiber's epoch changes
        // because _execute checks runner.epoch against the captured oldEpoch
      }
    })
  },
}

// Plugin that depends on `ticker` and triggers epoch changes
export const controller = {
  name: 'controller',
  inject: { ticker: true },
  async callback(ctx) {
    ctx.fiber.effect(() => {
      // When configuration changes, restart updates the epoch,
      // causing ticker's async loop to abort and recreate
      ctx.ticker?.on('configChanged', () => ctx.fiber.restart())
    })
  },
}

// Initialize the application
createApp({
  plugins: [ticker, controller],
}).then(app => app.start())

```

In this implementation, the `ticker` plugin creates an async generator effect that yielding control back to the runner. The internal `_execute` method in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts) handles the epoch comparison on every iteration. When `controller` detects a configuration change and calls `ctx.fiber.restart()`, Cordis invokes `_setEpoch` with a new value, causing the old generator to abort at the next iteration check and a fresh effect to start with the updated context.

## Summary

- The **epoch** is a `string | boolean` identifier stored in the `EffectRunner` class ([`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts)), tracking the generation of active effects.
- The **`_setEpoch`** method computes new epoch values based on dependency states (empty string for clean starts, colon-separated IDs for dependencies, or `INACTIVE` for missing requirements) and triggers `_reload` or `_unload` transitions.
- The **`_execute`** method captures the epoch before invoking effects and continuously checks `runner.epoch !== oldEpoch`, aborting stale asynchronous work immediately upon mismatch.
- The **`inject`** dependency map works with **`_refresh`** to propagate epoch changes through the fiber tree, enabling composable architectures where plugins restart automatically when their dependencies change.
- Each effect runs in **isolation** tied to its specific epoch, preventing race conditions and ensuring deterministic lifecycle management across complex plugin compositions.

## Frequently Asked Questions

### What is an epoch in Cordis?

An epoch in Cordis is a generation identifier with the type `string | boolean` stored in the `EffectRunner` class at [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts) (lines 72-76). It functions as a version tag for active effects, distinguishing between different execution generations of a fiber to ensure obsolete async work can be identified and terminated.

### How does Cordis detect when to restart a fiber?

Cordis detects restart conditions through the `_setEpoch` method in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts). When dependencies change via the `inject` map or when `ctx.fiber.restart()` is called, `_setEpoch` computes a new epoch value (lines 386-396) and compares it to the previous state. If the fiber was `INACTIVE` and becomes active, or vice versa, the system triggers `_reload` or `_unload` respectively (lines 405-411).

### Why does the epoch prevent stale async operations?

The epoch prevents stale operations through the check implemented in the `_execute` method (lines 263-267). Before running an effect, `_execute` stores the current epoch as `oldEpoch`. During every step of async iteration, the runner verifies that `runner.epoch !== oldEpoch`. If the values differ—meaning a new epoch has started—the iteration aborts immediately, guaranteeing that only the current generation's code continues executing.

### How does the epoch mechanism support plugin composition?

The epoch mechanism supports composition by allowing fibers to declare dependencies via the `inject` map. When a dependency's epoch changes, Cordis calls `_refresh` on dependent fibers, updating their epochs and triggering restarts. Because each effect is scoped to a specific epoch, nested and parallel effects do not interfere with each other, enabling developers to safely compose complex plugin trees where changes propagate deterministically without manual cleanup or race condition management.