npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@perufitlife/durable-agent-runtime

v0.1.0

Published

Stop your AI agent from doing the same thing twice, doing dumb things, or doing things you never agreed to. A durable runtime for LLM agents, in Postgres.

Readme

Durable Agent Runtime

Stop your AI agent from doing the same thing twice, doing dumb things, or doing things you never agreed to.

A durable runtime for LLM agents. Postgres-backed. Model-agnostic. ~5KB gzipped. Production-tested on a real CRM serving 10k+ daily WhatsApp conversations.

npm install @perufitlife/durable-agent-runtime

The 30-second pitch

Your LLM agent has tools. It calls them. Sometimes it calls the same tool twice (you get charged twice). Sometimes it calls a tool with bad inputs (you ship to a city that doesn't exist). Sometimes it does things you explicitly told it not to (it auto-confirms a payment).

This package wraps your tools in three layers:

  1. Idempotency — same tool, same input, same window → executes ONCE, returns cached result the next N times.
  2. Constitutional rules — predicates you write in TypeScript. Tool can't execute unless rules pass.
  3. Event log — every tool call is an immutable row in Postgres. Audit trail, replay, postmortem in 30 seconds instead of 8 hours of reading chats.

That's it. No new infrastructure. No vendor lock-in. Works with Claude, OpenAI, Gemini, local models — anything that calls a function.


Why this exists

We run a Peruvian COD e-commerce CRM with an LLM agent handling thousands of WhatsApp conversations per day. We learned the hard way:

| Real incident | What happened | What DAR does | |---|---|---| | 551 duplicate orders | A cron retried 19 times in a window. The "import to courier" tool fired all 19 times. 29 orders × 19 ticks = 551 duplicates. | Idempotency key import:order-123:hash makes call #2..19 return the cached result instantly. | | Zombie orders in Yunguyo | Agent promised home delivery in a city where no courier had coverage. Customer paid the advance. No one ever shipped. | Constitutional rule: dispatch_order requires validate_coverage(zone) === true. Tool call rejected before execution. | | Brand identity leak | Agent introduced itself as "Lucía from Peru Fit Life Store" when the actual Shopify store was a different brand. Customers got confused. | Rule: every outbound message must match store_config.brand_name. Mismatched message rejected with reason logged. |

Each of these cost real money. Each was preventable. Each is now impossible with DAR enabled.


Quickstart

1. Run the migration

-- migrations/001_init.sql
CREATE TABLE agent_tool_events (
  event_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  idempotency_key   TEXT NOT NULL UNIQUE,
  tool_name         TEXT NOT NULL,
  effect            TEXT NOT NULL,
  input             JSONB NOT NULL,
  output            JSONB,
  status            TEXT NOT NULL,
  rejection_reason  TEXT,
  correlation_id    TEXT,
  parent_event_id   UUID REFERENCES agent_tool_events(event_id),
  created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  completed_at      TIMESTAMPTZ
);

CREATE INDEX idx_agent_tool_events_tool_name ON agent_tool_events(tool_name);
CREATE INDEX idx_agent_tool_events_correlation_id ON agent_tool_events(correlation_id);
CREATE INDEX idx_agent_tool_events_created_at ON agent_tool_events(created_at DESC);

2. Wrap your tools

import { createRuntime, defineTool, defineRule } from '@perufitlife/durable-agent-runtime';
import { PostgresEventStore } from '@perufitlife/durable-agent-runtime/postgres';
import { Pool } from 'pg';

const runtime = createRuntime({
  store: new PostgresEventStore(new Pool({ connectionString: process.env.DATABASE_URL })),
});

// 1. Declare your tool with its effect type
const dispatchOrder = defineTool({
  name: 'dispatch_order',
  effect: 'irreversible',          // ⚠️ tells DAR to be strict
  idempotencyKey: (input) => `dispatch:${input.orderId}:${input.courier}`,
  handler: async (input: { orderId: string; courier: string; zone: string }) => {
    // your actual side effect
    return await courierAPI.createShipment(input);
  },
});

// 2. Add constitutional rules
runtime.addRule(defineRule({
  name: 'courier-must-cover-zone',
  appliesTo: 'dispatch_order',
  check: async (input) => {
    const covered = await coverageService.isCovered(input.courier, input.zone);
    return covered
      ? { allow: true }
      : { allow: false, reason: `${input.courier} does not cover ${input.zone}` };
  },
}));

// 3. Execute through the runtime
const result = await runtime.execute(dispatchOrder, {
  orderId: 'ORD-123',
  courier: 'shalom',
  zone: 'yunguyo',
});

// If the rule fails → result.status === 'rejected', courier API never called.
// If the rule passes and you call this again with the same input → result.status === 'cached'.
// If it's a fresh call → result.status === 'executed'.

What "durable" means here

Most agent frameworks treat tool calls as fire-and-forget. DAR treats them as event-sourced:

┌──────────────┐    ┌───────────────┐    ┌──────────────┐    ┌──────────────┐
│ LLM decides  │───▶│ DAR checks    │───▶│ DAR checks   │───▶│ Tool handler │
│ to call tool │    │ rules         │    │ idempotency  │    │ executes     │
└──────────────┘    └───────────────┘    └──────────────┘    └──────────────┘
                            │                    │                   │
                            ▼                    ▼                   ▼
                    ┌─────────────────────────────────────────────────────┐
                    │       agent_tool_events  (Postgres)                 │
                    │  every attempt, rejection, result — immutable row   │
                    └─────────────────────────────────────────────────────┘

This gives you:

  • Crash safety — your process dies mid-execution? The next replay reads the event log and skips completed work.
  • Idempotent retries — caller can retry safely without fear of duplicate effects.
  • Auditable historySELECT * FROM agent_tool_events WHERE correlation_id = 'session-xyz' reconstructs the entire agent reasoning trace.
  • Hot-reloadable rules — change predicates.ts, redeploy. Next tool call uses new rules. Zero downtime.

The Effect type system

Every tool declares one of four effects. DAR applies appropriate strictness:

| Effect | Examples | DAR behavior | |---|---|---| | read | get_order, search_customers | No idempotency required. Rules optional. | | mutate-local | update_internal_status, log_event | Idempotency required. Rules optional. | | mutate-external | send_whatsapp, charge_payment, update_shopify | Idempotency required. Rules strongly recommended. | | irreversible | dispatch_to_courier, send_email, fire_webhook | Idempotency required. Rules required. Sandbox dry-run available (v0.2). |

You can't ship a tool without declaring its effect. TypeScript enforces it. Reviewers see it at a glance.


Comparison

| | LangChain / LlamaIndex | Temporal / Restate | DAR | |---|---|---|---| | Idempotent tool calls | ❌ | ✅ (heavy) | ✅ | | Constitutional rules | ❌ | ❌ | ✅ | | Postgres-only (no new infra) | ❌ | ❌ (own cluster) | ✅ | | Model-agnostic | partial | yes | ✅ | | Bundle size | huge | huge | ~5KB gzipped | | Audit trail | ❌ | ✅ | ✅ | | Designed for LLM agents specifically | ✅ | ❌ | ✅ |

We're not trying to replace Temporal for microservices. We're solving the specific shape of "LLM picks a tool, calls it, sometimes does dumb things."


Production status

  • Battle-tested: serving production traffic at Peru Fit Life and FitCRM since May 2026.
  • API stability: 0.x — expect breaking changes until 1.0. We tag every minor with migration notes.
  • License: MIT.
  • Built by: @renzomacar — building in public, follow along.

What's next

  • v0.2 — Sandbox dry-run for irreversible tools (simulate before commit)
  • v0.3 — Saga compensation (multi-step rollback)
  • v0.4 — Semantic deduplication via embeddings
  • v0.5 — Replay graph visualizer (TUI + web)
  • v1.0 — Speculative branching (Monte Carlo Tree Search applied to tool choice)

PRs welcome. Issues even more welcome.


TL;DR — your LLM agent will eventually screw something up. DAR is the airbag, the seatbelt, and the dashcam.