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

@layerless/core

v1.3.1

Published

The serverless-native agent framework. Zero schema migration. Runtime contracts. Built-in analytics. RAG support.

Readme

Layerless

The serverless-native agent framework.

LangGraph runs on servers. Mastra runs in containers. Layerless runs in functions — by design, not by patching.

npm install @layerless/core

v1.2 — Runtime contracts, built-in analytics, RAG support, Schema Auto-Adapter CLI.


The problem

Every major agent framework was built for persistent servers. Deploy them on Vercel, AWS Lambda, or Cloudflare Workers and you hit the same wall:

  • Cold start kills state — function wakes up with no memory of previous steps
  • Timeout kills runs — complex agents need 60–120s, serverless limits are 10–60s
  • No intent reset — user sends a new message mid-run, agent ignores it or crashes
  • Invisible cost — no per-run token tracking, surprise bills

Layerless solves all of these at the framework level.


Quick start

import {
  defineState, defineGraph, defineNode,
  createRunner, MemoryStore, createLlmClient,
  messagesReducer, START, END,
} from "@layerless/core";

// 1. Define state
const AgentState = defineState({
  messages: { type: "value", default: [], reducer: messagesReducer },
  result:   { type: "value", default: "" },
});

// 2. Define nodes
const classify = defineNode("classify", {
  type: "deterministic",
  execute: async (state) => ({
    intent: state.messages.at(-1)?.content.includes("research") ? "research" : "chat",
  }),
});

const chat = defineNode("chat", {
  type:       "ai",
  model:      "gemini-2.5-flash-preview-04-17",
  system:     "You are a helpful assistant.",
  onComplete: async (result) => ({ result: result.text ?? "" }),
});

// 3. Wire the graph
const graph = defineGraph({ id: "my-agent", state: AgentState, llm: createLlmClient() })
  .addNode(classify).addNode(chat)
  .addEdge(START, "classify")
  .addConditionalEdge("classify", (s) => s.intent === "research" ? "research" : "chat")
  .addEdge("chat", END)
  .compile({
    store:         new MemoryStore(),   // VercelKVStore in production
    tickBudgetMs:  45_000,              // yield 5s before Vercel's 50s limit
    maxTotalSteps: 20,
  });

// 4. Run
const agent = createRunner(graph);
const done  = await agent.invoke({
  input: { messages: [{ role: "user", content: "What is serverless?" }] },
});
console.log(done.state.result);

Core concepts

State survives cold starts

Every state mutation auto-persists. No saveSession(). No manual checkpoints.

// This is ALL you need — state is saved after every node, automatically
const store = new VercelKVStore({ kv });

Tick budgeting

Set tickBudgetMs to yield gracefully before your platform's timeout kills the function. The agent resumes exactly where it left off on the next invocation.

.compile({
  tickBudgetMs: 45_000,  // yield 5s before Vercel's 50s limit
  maxTotalSteps: 40,
})

Three node types

// Deterministic — no LLM, runs first, cheapest
const classify = defineNode("classify", {
  type: "deterministic",
  execute: async (state) => ({ intent: detectIntent(state) }),
});

// AI — owns the LLM call + full tool loop
const researcher = defineNode("researcher", {
  type:   "ai",
  model:  "claude-sonnet-4-20250514",
  system: "You are a thorough researcher.",
  tools:  [searchWeb, readPage],
});

// Interrupt — pauses run, waits for human approval
const review = defineNode("review", {
  type:        "interrupt",
  onInterrupt: async (state, ctx) => notify.send({ runId: ctx.runId }),
});

Parallel agents

Run multiple agents simultaneously, merge their results.

graph
  .addEdge(START, "classify")
  .addParallelEdge("classify", ["researcher", "fact_checker"])  // run in parallel
  .addMergeEdge(["researcher", "fact_checker"], "writer")       // merge results
  .addEdge("writer", END)

Agent memory

Agents remember across runs. Per-user, per-agent, or per-session.

const node = defineNode("chat", {
  type: "ai", model: "gemini-2.5-flash-preview-04-17",
  system: "You are a helpful assistant.",
  execute: async (state, ctx) => {
    // Load past conversation
    const history = await ctx.memory.loadHistory({
      scope: "user", scopeId: ctx.metadata.userId as string,
    });
    return { messages: history };
  },
  onComplete: async (result, state, ctx) => {
    // Save this exchange
    await ctx.memory.saveMessage(
      { role: "assistant", content: result.text ?? "" },
      { scope: "user", scopeId: ctx.metadata.userId as string },
    );
    return { result: result.text ?? "" };
  },
});

// Add memory store to graph
defineGraph({ id: "agent", state, llm, memoryStore: new KVMemoryStore({ kv }) })

Dynamic agent spawning

One agent spawns specialists at runtime based on the task — this is CrewAI's "hierarchical process", built for serverless.

import { SpawnRegistry } from "@layerless/core";

// 1. Define specialist sub-agents
const researchGraph   = defineGraph({ id: "researcher",   ... }).compile({ ... });
const factCheckGraph  = defineGraph({ id: "fact_checker", ... }).compile({ ... });
const writerGraph     = defineGraph({ id: "writer",       ... }).compile({ ... });

// 2. Register them
const registry = new SpawnRegistry();
registry.register("researcher",   researchGraph);
registry.register("fact_checker", factCheckGraph);
registry.register("writer",       writerGraph);

// 3. Orchestrator spawns at runtime
const orchestrator = defineNode("orchestrator", {
  type:    "ai",
  model:   "gemini-2.5-flash-preview-04-17",
  system:  "You are an orchestrator. Delegate to specialists.",
  execute: async (state, ctx) => {
    // Each spawn runs inline and returns its final state
    const research = await ctx.spawn("researcher",   { input: { topic: state.query }, maxSteps: 10 });
    const verified = await ctx.spawn("fact_checker", { input: { content: research.state.result }, maxSteps: 5 });
    const article  = await ctx.spawn("writer",       { input: { facts: verified.state.result }, maxSteps: 5 });
    return { result: article.state.result };
  },
});

// 4. Add registry to graph config
defineGraph({ id: "orchestrator", state, llm, spawnRegistry: registry })

Sub-agents:

  • Share the parent's store and memory
  • Have their own token budget (tracked separately)
  • Can spawn their own sub-agents (up to depth 5)
  • Return { state, runId, totalSteps, durationMs, estimatedCostUsd, status }

Token budgeting

Know exactly what every run costs. Yield or abort when limits are hit.

defineGraph({
  id: "agent", state, llm,
  budget: {
    maxTokensPerRun:  50_000,
    maxCostPerRun:    0.10,      // $0.10 hard cap
    onBudgetExceeded: "yield",
  },
})

LLM providers

Layerless works with all major providers. Switch by changing one line.

import { createLlmClient } from "@layerless/core";

// Auto-detects from model name
const llm = createLlmClient({ model: "gemini-2.5-flash-preview-04-17" });
const llm = createLlmClient({ model: "claude-sonnet-4-20250514" });
const llm = createLlmClient({ model: "gpt-4o" });

// Or explicit
import { AnthropicClient, GeminiClient, OpenAIClient } from "@layerless/core";
const llm = new GeminiClient({ apiKey: process.env.GEMINI_API_KEY });

Set the matching environment variable:

GEMINI_API_KEY=AIza...
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

Next.js integration

npm install @layerless/next
// app/api/agent/route.ts
import { createAgentRoute } from "@layerless/next";
import { agent } from "@/lib/agent";

export const maxDuration = 60;
export const { POST } = createAgentRoute(agent, {
  onYield: async ({ runId }) => {
    // Re-queue for continuation when tick budget is hit
    await fetch("/api/agent/continue", {
      method: "POST",
      body: JSON.stringify({ runId }),
    });
  },
});
// app/api/agent/continue/route.ts
import { createContinuationRoute } from "@layerless/next";
import { agent } from "@/lib/agent";

export const maxDuration = 60;
export const { POST } = createContinuationRoute(agent);

Store adapters

import { MemoryStore }    from "@layerless/core";           // dev/testing
import { VercelKVStore }  from "@layerless/core";           // Vercel KV (Upstash)
// Vercel KV
import { kv } from "@vercel/kv";
const store = new VercelKVStore({ kv, namespace: "my-agent", ttlSeconds: 604800 });

Dev server

npx layerless dev

Opens http://localhost:4000 with:

  • Live run list with status dots
  • Event stream per run (checkpoints, node timing, errors)
  • State snapshot tab — see exactly what's in state after each node
  • Budget tab — total tokens, cost breakdown per node
// layerless.config.ts (project root)
import { defineGraph, ... } from "./packages/core/src/index.js";
export default graph.compile({ ... });

What makes it different

| Feature | LangGraph | Mastra | Layerless | |---|---|---|---| | Cold start survival | ❌ manual | Via Inngest | ✅ built-in | | Tick budgeting | ❌ | ❌ | ✅ | | Optimistic locking | ❌ | ❌ | ✅ versioned | | Parallel agents | ❌ | ❌ | ✅ fan-out/fan-in | | Agent memory | ❌ | ❌ | ✅ scoped | | Context compression | ❌ | ❌ | ✅ sliding-window | | Token/cost budgeting | ❌ | ❌ | ✅ per run | | Intent reset | ❌ | ❌ | ✅ configurable | | Dynamic spawning | ✅ | ❌ | ✅ | | Built-in analytics | ❌ | ❌ | ✅ your DB | | RAG node type | ❌ | ❌ | ✅ any store | | Schema Auto-Adapter | ❌ | ❌ | ✅ CLI | | Next.js native | ❌ | Partial | ✅ first-class | | Local dev server | ❌ | ❌ | ✅ with replay |


Built-in analytics

Zero-config observability. Data lives in YOUR database. No LangSmith subscription needed.

import { MemoryAnalyticsStore, SupabaseAnalyticsStore } from "@layerless/core";

// Dev: in-memory
const analytics = new MemoryAnalyticsStore();

// Production: your own Supabase
const analytics = new SupabaseAnalyticsStore({ supabase, runsTable: "layerless_runs" });

defineGraph({ id: "pipepal", state, llm, analytics })

Query your agent's performance from anywhere:

const mgr = new AnalyticsManager(analytics, "pipepal");

// High-level health summary
const stats = await mgr.summary({ from: "2026-04-01" });
// { totalRuns: 1240, successRate: 0.97, avgCostUsd: 0.0003, p95LatencyMs: 2100 }

// Which nodes are slow or expensive?
const nodes = await mgr.byNode();
// { chat: { runs: 892, avgDurationMs: 1200, avgCostUsd: 0.00028, contractViolations: 3 } }

// What broke in the last hour?
const errors = await mgr.errors({ from: Date.now() - 3600_000 });

Required SQL (run once in Supabase):

create table layerless_runs  ( run_id text primary key, graph_id text, status text, ... );
create table layerless_nodes ( id bigserial primary key, run_id text, node_id text, ... );

RAG support

RAG as a first-class node type. Works with any vector store. Retrieved context is saved in state and survives cold starts.

import { PineconeAdapter, SupabaseVectorAdapter } from "@layerless/core";

// Pinecone
const vectorStore = new PineconeAdapter({
  index:       pinecone.index("docs"),
  embeddingFn: (text) => openai.embeddings.create({ input: text, model: "text-embedding-3-small" })
                           .then((r) => r.data[0].embedding),
});

// Supabase pgvector (free tier works)
const vectorStore = new SupabaseVectorAdapter({
  supabase,
  table:       "documents",
  embeddingFn: (text) => openai.embeddings.create(...).then((r) => r.data[0].embedding),
});

// RAG node — retrieves context, stores it in state, tracks cost
const retrieve = defineNode("retrieve_context", {
  type:        "rag",
  vectorStore,
  topK:        5,
  queryFrom:   (state) => state.messages.at(-1)?.content ?? "",
  storeAs:     "context",
  minScore:    0.7,
  contracts: {
    post: [{ check: (s) => s.context.length > 0, message: "Must retrieve at least one result" }],
  },
});

Schema Auto-Adapter CLI

The only framework that adopts YOUR database schema — not forces you to adopt ours.

npx @layerless/cli init --db postgres://user:pass@host/db --table sessions

Reads your real table, samples 3 rows, infers your JSON blob structure, generates a working TypeScript store adapter in 30 seconds. Zero schema changes.

# Also available:
npx @layerless/cli dev                           # Start local dev server
npx @layerless/cli replay --runId run_17759...   # Replay a production run locally

API reference

defineState(schema)

Define the typed state for your agent graph.

defineNode(id, def)

Define a graph node. Types: deterministic | ai | interrupt.

defineGraph(config)

Create a graph builder. Chain .addNode(), .addEdge(), .addParallelEdge(), .addMergeEdge(), .addConditionalEdge(), then .compile().

compile({ store, tickBudgetMs, maxTotalSteps, ... })

Validate and lock the graph. Required fieldsstore, tickBudgetMs, maxTotalSteps. The framework won't let you skip these.

createRunner(graph)

Create a runnable agent from a compiled graph.

agent.stream(options)

Run the agent, yielding StreamEvent objects. Handles start, resume, yield, interrupt.

agent.invoke(options)

Like stream() but waits for completion and returns the final checkpoint.

agent.getSnapshot(runId)

Retrieve the current checkpoint for any run.

ctx.memory

Available inside every node.

ctx.spawn(agentName, options)

Dynamically spawn a named sub-agent at runtime. Returns SpawnResult with state, runId, totalSteps, durationMs, estimatedCostUsd, status.

SpawnRegistry

Register named sub-agent graphs. Pass to defineGraph({ spawnRegistry: registry }). remember(), recall(), append(), search(), loadHistory(), saveMessage().


License

MIT — see LICENSE