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

@merkie/agentic

v0.5.1

Published

A resilience, storage, and observability harness for LLM agents on the Vercel AI SDK + OpenRouter — durable resumable sessions, guaranteed-outcome tasks, smart retries, token-based compaction, and BYOK-aware cost tracking.

Readme

@merkie/agentic

A resilience, storage, and observability harness for LLM agents on the Vercel AI SDK + OpenRouter.

Most AI SDK setups are bring-your-own-everything: every project re-implements retry loops, chat persistence, crash recovery, compaction, and cost tracking — slightly differently, with the same bugs. @merkie/agentic ships that plumbing once, battle-tested, so your code is just models, prompts, and tools.

What the harness guarantees:

  • Runs don't fail when waiting a second would have saved them. Transient provider errors (429s, 5xxs, severed SSE streams, stalled connections) are classified and retried with capped exponential backoff (server Retry-After wins). Deterministic errors — billing, auth, policy, malformed requests, context overflow — fail fast instead of burning credits in a retry loop.
  • Runs survive process restarts — automatically. Every model step is persisted the moment it finishes; the agent loop is stateless over an append-only event ledger, so recovery from a SIGKILL mid-run is just "run the loop again". With autoResume configured, the harness sweeps storage on boot and re-drives interrupted work by itself, with four resumed runs in flight by default and a ledger-counted attempt cap so a run that crashes the process on resume can't wedge it into a restart loop. Bring your own storage (Prisma, SQLite, Redis…) by implementing two methods; JSONL file storage is built in.
  • Workflows have guaranteed outcomes. task() gives the model submit_deliverable + cancel_task, validates the deliverable with zod inside the tool (validation errors go back to the model as tool results it can fix — no memoryless structured-output retries), and pokes the model if it ends its turn without calling a terminal tool. You always get submitted | cancelled | failed, never a throw.
  • Messages sent mid-run queue into the run — durably. A send() while the agent is working is appended to the ledger first (a crash can never drop it), then folded into the live run at its next step boundary; the run doesn't end while unanswered input is waiting. Queued messages orphaned by a restart are picked up by resume() like any interrupted work. Concurrent callers whose inputs are handled in one model pass all receive that pass's real terminal status, including a later failure or cancellation.
  • Chats outlive the context window. Compaction triggers on real provider-reported token counts against the model's actual context window (fetched from OpenRouter), summarizes into a hand-off message, and keeps going — silently between turns, or mid-run for agents deep in a task.
  • Cost is tracked correctly, including BYOK: OpenRouter credits report cost; BYOK reports the provider charge in upstream_inference_cost. Per-step usage/cost is persisted, aggregated per run and per session.

Install

npm install @merkie/agentic ai @openrouter/ai-sdk-provider zod

ai (v6), @openrouter/ai-sdk-provider, and zod (v4) are peer dependencies. Reads OPENROUTER_API_KEY from the environment by default.

The harness in 30 seconds

import { createAgentic, fileStorage } from "@merkie/agentic";
import { tool } from "ai";
import { z } from "zod";

const agentic = createAgentic({
  storage: fileStorage("./.agentic"),
  // crash/deploy recovery, no boot code: the harness sweeps storage for
  // interrupted work and resumes it, using this to map ids back to configs
  autoResume: (sessionId) => myAgentFor(sessionId),
});

// ── durable chat ──────────────────────────────────────────────────────
const chat = agentic.session("chat:user-123", {
  model: "qwen/qwen3.7-max",
  system: "You are a helpful assistant.",
  tools: { /* your tools */ },
  compaction: { limit: 0.3 },          // compact at 30% of context window
});
const reply = await chat.send("hey!", { onPart: (p) => {/* stream to UI */} });

// ── workflow task with a guaranteed outcome ───────────────────────────
const outcome = await agentic.task({
  agent: {
    model: "qwen/qwen3.7-max",
    system: "You are a bank task worker.",
    tools: { get_account },
  },
  prompt: "Look up ACC-1001's balance for its authenticated owner and submit it.",
  deliverable: z.object({ accountId: z.string(), balance: z.number() }),
});
// outcome.status: "submitted" (typed deliverable) | "cancelled" (model's
// escape hatch, with reason) | "failed" (bounded retries exhausted)

Every session — chats, workflows, one-shots — shares the same ledger, so all of it is resumable, auditable, and cost-tracked. Observability is one flag — or one hook:

createAgentic({ logs: true })            // colored console line per event
createAgentic({ onEvent: (e) => log(e) })  // or ship them anywhere
// run-start · step · retry · compaction · poke · queued-message ·
// auto-resume · run-end

Create one Agentic instance per process for a given storage provider, then differentiate workloads with session-id namespaces and per-session agent configs. Locks, live-run mailboxes, and recovery state are instance-local, so two instances writing the same storage can start competing runs for one session.

For recovery tuning, pass autoResume: { agentFor, maxAttempts, maxConcurrent, staggerMs }. maxConcurrent defaults to 4; staggerMs still spaces the start of each resumed run, while the concurrency cap prevents a large interrupted backlog from creating an unbounded provider stampede.

The built-in storage providers preserve binary image/audio parts. Custom storage adapters should persist events with encodeEvent(event) and restore them with decodeEvent(json) instead of plain JSON serialization. Framework poke reminders are stored as user-message events with meta.poke; filter those when projecting a user-visible transcript. Compaction summarizes older multimodal turns as text, so binary parts only remain verbatim when they fall inside keepRecent.

Queued-input causality is recorded on each step; raw append order alone is not a transcript (an in-flight step can be persisted after a newly arrived user message without having seen it). Use session.messages() or replaySession() for projections. A durable non-error step, including a tool call/result step, counts as having handled the queued input it records. If the run later fails, callers receive that failure and the input is not replayed automatically, avoiding duplicate tool side effects. An input that only saw an empty/error step remains pending for recovery.

onPart is a best-effort live transport, not a durable subscription. A caller that reconnects—or whose queued input is picked up by a different serialized run—should use the resolved RunResult and session.messages() as the source of truth; historical stream parts are not replayed from storage.

When an automatic recovery reaches maxAttempts, queued-only work remains in the ledger and can still be retried with manual resume(); the cap prevents a boot loop rather than deleting accepted input.

À-la-carte helpers

Everything the harness is built from is exported for use with plain streamText/generateText:

| Helper | What it does | |---|---| | createOpenRouter | drop-in provider factory with usage accounting on | | logEvents | the default console logger (logs: true), usable as an onEvent directly | | withRetries(fn) | retry any model call on transient failures, fail fast on deterministic ones | | classifyFailure(err) | transient | context-overflow | fatal (+ Retry-After) | | createResilientFetch | header + SSE-idle stall detection for hung connections | | sanitizeConversation | heal interrupted/malformed tool-call transcripts before replay | | guardToolResultSizes | cap tool results so one result can't blow the context window | | extractStepUsage | per-step tokens + BYOK-reconciled cost from provider metadata | | getContextWindow | a model's context length from OpenRouter, memoized | | encodeEvent / decodeEvent | binary-safe StoredEvent serialization for custom adapters | | serializedStorage | serialize custom-adapter operations per session |

Local playground

playground/mvp/ has runnable proof demos (bring an OPENROUTER_API_KEY in .env):

npx tsx playground/boilerplate.ts          # the smallest useful setup — copy me
npx tsx playground/mvp/demo-task.ts        # schema self-heal + guaranteed outcome
npx tsx playground/mvp/demo-media-task.ts <image> # validated multimodal task
npx tsx playground/mvp/demo-chaos.ts       # injected 500s + severed SSE mid-run
npx tsx playground/mvp/demo-restart.ts     # SIGKILL mid-run → autoResume in new process
npx tsx playground/mvp/demo-compaction.ts  # memory survives two compactions
npx tsx playground/mvp/demo-queue.ts       # send() mid-run queues into the live run
npx tsx playground/mvp/before-after/before.ts  # the plumbing you'd write by hand
npx tsx playground/mvp/before-after/after.ts   # the same workflow on the harness

Development

npm test          # vitest
npm run typecheck
npm run build     # tsup → dist/

License

MIT © Merkie