Alvyn logoAlvyn

Introduction

TypeScript and PostgreSQL event history for agent workflows and domain applications, with typed aggregates, replay, projections, snapshots, and an outbox.

Alvyn is a TypeScript library for event history on PostgreSQL. Record meaningful facts, rebuild application state, and inspect how a workflow reached its current state. AI agents are an approachable starting point; orders, approvals, billing, and other domain applications use the same primitives. You do not need CQRS knowledge or an all-at-once rewrite.

Use Alvyn in an application

npm install alvyn pg

You supply and close the connection pool. This snippet records a goal and a known result; it does not call a model or execute a tool. See the Agent History, Replay & Audit playbook for model integration boundaries.

import { Pool } from "pg";
import { EventStore, defineAggregate } from "alvyn";

type AgentEvents = {
  GoalReceived: { goal: string };
  RunCompleted: { output: string };
};
type AgentState = { goal: string | null; output: string | null };

const AgentRun = defineAggregate<AgentState, AgentEvents>()({
  streamPrefix: "AgentRun",
  evolve: {
    GoalReceived: (_state, event) => ({
      goal: event.data?.goal ?? null,
      output: null,
    }),
    RunCompleted: (state, event) => ({
      goal: state?.goal ?? null,
      output: event.data?.output ?? null,
    }),
  },
});

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
try {
  const store = new EventStore({ pool });
  await store.setup();
  const runId = crypto.randomUUID();
  const created = await AgentRun.append(store, {
    entityId: runId,
    expectedVersion: -1,
    events: [
      { type: "GoalReceived", data: { goal: "Find the returns policy" } },
    ],
  });
  await AgentRun.append(store, {
    entityId: runId,
    expectedVersion: created.toVersion,
    events: [
      {
        type: "RunCompleted",
        data: { output: "Returns accepted for 30 days." },
      },
    ],
  });
  console.log((await AgentRun.load(store, runId)).state);
} finally {
  await pool.end();
}

The first evolve handler receives null at runtime and must initialize the whole state. An empty aggregate loads with state: null and version: 0; use expectedVersion: -1 to explicitly create a new stream. Later appends use the loaded version or the previous append's toVersion. Define a handler for every event in the event map.

Building blocks

  • Typed aggregates group related facts into streams and rebuild current state.
  • Historical replay uses load or loadFrom plus your reducer over an explicit event prefix; current aggregate load is not a historical query.
  • Snapshots cache expensive derived state as separate events.
  • Subscriptions and projections support resumable consumers and query-oriented read models.
  • Transactional outbox records delivery work atomically with events; delivery is at least once and external effects need idempotency.
  • Schema evolution applies read-time upcasters to older event shapes.
  • Encryption and key revocation support privacy workflows when explicitly configured.

Replay reproduces reducer state from retained, readable events, not a deterministic model rerun. Only instrumented actions are recorded, not hidden thoughts. Event history is neither automatic regulatory compliance nor tamper-proof evidence. Encryption does not automatically cover snapshots, projections, logs, backups, or provider copies; deletion can make original state unreconstructable.

Requirements

  • Node.js matching the package engines: ^22.18.0 || ^24.11.0 || >=26.0.0.
  • PostgreSQL 14 or later for persistence.
  • pg 8 as a peer dependency.

Next steps

On this page