How Magnitude Event‑Core Projections Enable Session Replay: Window, Fork, and TaskGraph Explained

Magnitude's event‑core package uses immutable event streams and deterministic projections—including Window, Fork, and TaskGraph types—to reconstruct any point‑in‑time session state for reliable replay.

The event‑core system in magnitudedev/magnitude implements event‑sourcing architecture where projections act as pure reducers that materialize read‑only views from event streams. Understanding how event‑core projections work is essential for building debuggable, replayable applications—particularly for AI workflows requiring complex state inspection.

Core Projection Types in Event‑Core

Magnitude provides three specialized projection patterns that enable different replay scenarios. Each type lives in packages/event-core/src/projection/ and serves distinct use cases for session reconstruction.

Regular Projections with Projection.define

The foundation of the system is Projection.define, declared in [define.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/projection/define.ts). This creates non‑forked projections that maintain a single state value across all events.

A regular projection specifies:

  • The event types it consumes
  • An initial state schema
  • Event handler functions for state mutation
import { Projection } from '@magnitudedev/event-core';

type IncrementEvent = { type: 'increment'; amount: number };

const CounterProjection = Projection.define<IncrementEvent>()({
  name: 'Counter',
  init: () => ({ count: 0 }),
  handlers: {
    increment: (state, ev) => ({ count: state.count + ev.amount }),
  },
});

The handlers are pure functions—they receive the current state and event, returning new state without side effects. This determinism guarantees identical replay results.

Forked Projections with Projection.defineForked

Parallel workflows require fork‑aware projections via Projection.defineForked in [defineForked.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/projection/defineForked.ts). These maintain independent state per fork ID, enabling scenarios like:

  • User‑opened branches
  • Parallel AI agent workflows
  • Temporary sandboxed contexts
import { Projection } from '@magnitudedev/event-core';
import type { ForkWindowState } from '@magnitudedev/event-core';

type MyEvent = { type: 'message'; text: string; forkId: string | null };

const WindowProjection = Projection.defineForked<MyEvent, ForkWindowState>()({
  name: 'Window',
  init: () => ({ messages: [] }),
  handlers: {
    message: (state, ev) => ({
      messages: [...state.messages, ev.text],
    }),
  },
});

Forked projections expose getFork(forkId) and allForks for accessing specific branches. The replay engine routes events to the appropriate fork based on each event's forkId field.

TaskGraph Projections for DAG Workflows

The TaskGraph projection—conceptually located at task-graph.ts alongside other projection definitions—represents directed acyclic graphs of tasks. This enables replay of multi‑stage AI pipelines where tasks have dependencies and completion states.

type TaskEvent = { type: 'taskStarted' | 'taskFinished'; id: string; deps: string[] };

const TaskGraphProjection = Projection.define<TaskEvent>()({
  name: 'TaskGraph',
  init: () => ({ nodes: new Map<string, TaskNode>() }),
  handlers: {
    taskStarted: (state, ev) => {
      state.nodes.set(ev.id, { id: ev.id, deps: ev.deps, status: 'running' });
    },
    taskFinished: (state, ev) => {
      const node = state.nodes.get(ev.id);
      if (node) node.status = 'completed';
    },
  },
});

Each node stores its dependencies and status, allowing the engine to reconstruct the exact execution graph at any point during replay.

Window Projections: Addressed Sequence Access

Window projections provide addressed sequence windows—contiguous slices of ordered event streams without scanning entire logs. The implementation in [addressed/collections/sequence.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/addressed/collections/sequence.ts) exposes three key helpers:

Helper Purpose
resolveRangeWindow Get a specific index range
resolveTailWindow Get the last N items
readWindow Materialize the window from state
import { addressed } from '@magnitudedev/event-core';

// Get last 10 messages from replayed state
const tailWindow = addressed.messages.resolveTailWindow(state.messages, 10);
const recentMessages = yield* addressed.messages.readWindow(tailWindow);

Windows are computed logically rather than by event log traversal. This keeps memory usage bounded during replay and enables efficient state inspection.

How Session Replay Works Step‑by‑Step

The replay engine in [engine.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/engine.ts) orchestrates deterministic reconstruction. Here's the complete flow:

1. Event Ingestion and State Mutation

The EventBus publishes immutable events. Each projection—Window, Fork, or TaskGraph—registers as a consumer and updates its in‑memory state through pure handlers.

2. Snapshot Persistence

After configurable checkpoints, the engine writes projection snapshots containing:

  • Projection identifier
  • Fork ID (if applicable)
  • Serialized state via Schema.encode
  • Event index the snapshot represents

3. Replay Execution

When reconstructing state, the engine:

  1. Loads the latest snapshot ≤ target index
  2. Instantiates fresh projection instances with snapshot state
  3. Re‑applies events from snapshot.index + 1 to target index
import { engine } from '@magnitudedev/event-core';

const snapshot = await engine.loadSnapshot('session-1', 120);
const plan = await engine.prepareProjectionSnapshotRestore(snapshot, { targetIndex: 250 });
await plan.run(); // All projections now at index 250

This pattern—demonstrated in [invariants.test.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/projection/invariants.test.ts)—ensures deterministic reconstruction regardless of projection type.

4. Fork Access During Replay

For forked projections, access specific branches via:

const forkId = 'branch-42';
const forkInst = projectionInstance.getFork(forkId);
const forkState = yield* forkInst.read();

The engine recreates each fork's state independently from its snapshot.

Why This Design Enables Reliable Session Replay

Deterministic reducers eliminate side effects that could corrupt replay. The same event sequence always produces identical projection state.

Snapshot granularity minimizes replay overhead. Jumping to a near‑target checkpoint means processing only a small event tail.

Fork isolation prevents cross‑branch interference. Parallel workflows replay independently through separate state dictionaries.

TaskGraph expressiveness reconstructs not just linear state but execution topology—critical for debugging multi‑stage AI pipelines where task dependencies matter.

Summary

  • Projection.define creates regular projections with single state values in define.ts
  • Projection.defineForked enables parallel branch isolation via getFork/allForks in defineForked.ts
  • Window projections provide memory‑efficient sequence slices through resolveTailWindow and helpers in addressed/collections/sequence.ts
  • TaskGraph projections model DAG workflows for complex pipeline replay
  • Snapshot + replay engine in engine.ts delivers deterministic point‑in‑time reconstruction from any checkpoint

Frequently Asked Questions

What makes Magnitude projections "pure" and why does that matter for replay?

A projection is pure when its handlers are deterministic functions of (state, event) → new state with no side effects. This matters because the replay engine in engine.ts must guarantee that re‑executing the same event sequence produces identical state. Impure handlers—those with external I/O or randomness—would break this guarantee and corrupt session replay.

How does the Window projection avoid loading entire event histories?

Window projections use logical resolution via resolveRangeWindow and resolveTailWindow in sequence.ts. These helpers compute address descriptors—start index, end index, and slice parameters—without materializing data. Only readWindow touches actual state, and it reads from the already‑projected in‑memory representation rather than the event log. This keeps memory usage O(window size) rather than O(total events).

When should I use defineForked versus define for my projection?

Use defineForked when your domain has parallel execution contexts that must not interfere: user branches, A/B test variants, or sandboxed agent runs. Each fork maintains independent state under the same projection logic. Use define for global session state that all contexts share—like an overall session status or cross‑fork metrics. The fork ID in events (forkId: string | null) determines routing at replay time.

Can TaskGraph projections represent cyclic dependencies?

No—TaskGraph projections intentionally model DAGs (directed acyclic graphs). The deps: string[] field in task events establishes parent→child relationships. Cycles would create logical contradictions in replay: a task could not start until its dependency finished, yet that dependency depends on the original task. The projection handlers in the conceptual task-graph.ts assume acyclicity for deterministic topological ordering during state reconstruction.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →