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

@inngest/x-agents

v0.0.2-rc.0

Published

Thin, composable, forkable primitives for building agents on Inngest's durable execution model.

Readme

Inngest Agents SDK

A durable agent loop for TypeScript. Not a framework.

runAgentLoop() runs your agent inside an Inngest function. Every LLM call, every tool call, every retry is a durable step: your agent survives crashes, cold starts, and three-hour runs without losing state. Hit the context limit? The loop compacts and retries. Model down? It falls back. Rate limited? It waits. Invalid tool call? The error goes back to the model. You configure the behavior — the loop handles the failure. Read the whole core in 30 minutes.

Install

pnpm add @inngest/x-agents inngest

Published for internal Inngest usage and testing — see docs/publishing.md for how releases are built and shipped. Consumers should set "skipLibCheck": true in their tsconfig.json (transitive model-provider SDKs don't typecheck cleanly without it).

Quick start — runAgentLoop()

An agent is a loop inside an Inngest function:

import { Inngest } from "inngest";
import { createPiAdapter, runAgentLoop, type StepLike } from "@inngest/x-agents";

const inngest = new Inngest({ id: "my-app" });
const adapter = createPiAdapter("anthropic/claude-sonnet-4-6");

export const agent = inngest.createFunction(
  { id: "assistant", triggers: [{ event: "agent/run" }] },
  async ({ event, step }) => {
    const result = await runAgentLoop({
      step: step as unknown as StepLike,
      adapter,
      instructions: "You are a concise, helpful assistant.",
      messages: [{ role: "user", content: event.data.query, timestamp: Date.now() }],
    });
    return { text: result.text };
  },
);

That's the harness. Serve it like any Inngest function; every iteration is a memoized step you can watch, retry, and replay in the dashboard.

Adding tools

import { createDurableTool, createTool, Type } from "@inngest/x-agents";

// The default: the WHOLE handler runs in one memoized step — a side effect
// fires exactly once across replays.
const getWeather = createDurableTool({
  name: "get_weather",
  description: "Get the current weather for a city.",
  parameters: Type.Object({ city: Type.String() }),
  handler: async ({ city }) => fetchWeather(city),
});

// The escape hatch: the handler runs at loop level with a full `ctx.step`,
// so a tool can be a mini-workflow that owns its own durable steps.
const publish = createTool({
  name: "publish",
  description: "Render and upload a report.",
  parameters: Type.Object({ title: Type.String() }),
  handler: async ({ title }, ctx) => {
    const html = await ctx.step.run("render", () => render(title));
    return ctx.step.run("upload", () => upload(html));
  },
});

Pass tools: [getWeather, publish] to the loop. Gate anything risky with approval: { reason, timeout } — the run pauses durably for a human before the tool executes.

Any Inngest function is a tool — asAgentTool()

Sub-agents don't need a framework; they need step.invoke:

import { asAgentTool } from "@inngest/x-agents";

tools: [
  asAgentTool(researchAgent),                    // another agent
  asAgentTool(sendEmailFn),                      // a plain Inngest function
  asAgentTool(pipelineFn, { name: "run_analysis" }),
  asAgentTool(workerFn, { async: true, event: "work/queued" }), // fire-and-forget
]

The call becomes a step.invoke (or step.sendEvent) — retries, replay, and observability come from the platform, not from the Inngest Agents SDK.

createAgent() — the convenience wrapper

When you want the boring stuff handled — trigger events, sessions, delivery, flow control — wrap the same loop in config:

import { createAgent } from "@inngest/x-agents";
import { createFileSessionStore } from "@inngest/x-agents/session";

const assistant = createAgent({
  id: "assistant",
  model: "anthropic/claude-sonnet-4-6",
  instructions: "You are a concise, helpful assistant.",
  tools: [getWeather],
  session: createFileSessionStore({ dir: ".sessions", primary: "conversation_id" }),
  flowControl: { singleton: { key: "event.data.channelKey", mode: "cancel" } },
});

assistant.asTool() / assistant.asAsyncTool() wire it into another agent. Every run emits agent/run.completed — score runs with standalone Inngest scorer functions; the agent never knows it's being evaluated.

Capabilities — opt-in, composable

Each capability is a plain function returning tools, instructions, or hooks. No plugins, no registration — compose them yourself:

import { memoryTools } from "@inngest/x-agents/memory";
import { skillsInstructions, readSkillTool } from "@inngest/x-agents/skills";
import { realtimeHooks, slackHooks } from "@inngest/x-agents/delivery";
import { composeInstructions, mergeHooks } from "@inngest/x-agents";

createAgent({
  instructions: composeInstructions(base, skillsInstructions({ store })),
  tools: [...myTools, ...memoryTools({ store }), readSkillTool({ store })],
  hooks: (ctx) => mergeHooks(realtimeHooks(ctx), slackHooks({ token, channelKey: ctx.channelKey })),
});

More lines than plugins: [...]. More clarity. No magic.

Context — dependency injection for tools

The loop takes a generic ctx and hands it to every tool handler — a sandbox, a DB client, the current user. Tools declare what they need and TypeScript checks it against the loop's ctx. Generic onPause/onResume hooks fire around human waits, so live resources hibernate while a run sleeps on approval:

import { createSandboxContext, localSandbox } from "@inngest/x-agents/sandbox";
import { sandboxTools } from "@inngest/x-agents/tools";

const sb = await createSandboxContext(step, { provider: localSandbox({ root: ".ws" }), sessions });

await runAgentLoop({
  step, adapter, messages,
  ctx: { sandbox: sb.sandbox, db: prisma },   // any resources you like
  onPause: sb.onPause,                        // hibernate during HITL waits
  onResume: sb.onResume,
  tools: [...sandboxTools()],                 // typed: they require ctx.sandbox
});

There's no sandbox config field — a sandbox is just one thing you might put on ctx. See docs/context.md.

Patterns

Complete, forkable files in /patterns — basic loop, tools, multi-turn sessions, sub-agents, functions-as-tools, human approval, user-defined context, skills, memory, background agents. Copy one, modify it, ship it.

Repo

  • lib/ — the library (see ARCHITECTURE.md for contributor docs)
  • patterns/ — copy-paste starting points
  • example/ — a full end-to-end app (streaming TUI chat, sandbox, skills, memory)
  • examples/ — six applied apps (support bot, coding agent, Slack, code review, HITL, personal agent)
pnpm typecheck   # tsc --noEmit
pnpm smoke       # dependency-free smoke tests
pnpm example:serve && pnpm example:chat   # run the example app