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

@agentic-patterns/runtime

v0.40.0

Published

Agent execution runtime — runner, events, gates, multi-agent, conversation, exporters

Readme

@agentic-patterns/runtime

Execution runtime for agentic-patterns agents. Provides the runner loop (Vercel AI SDK), typed event bus, gate chain, workflow compositions and loops, multi-agent transport and runtime, conversation persistence, observability exporters, and pre-built role presets.

Installation

bun add @agentic-patterns/runtime @agentic-patterns/core ai zod

API Overview

Runner (src/runner/)

The AgentRunner executes agents using a tool loop on the Vercel AI SDK.

import { AgentRunner } from "@agentic-patterns/runtime";

const runner = new AgentRunner(model, eventBus);

const result = await runner.run(agent, "Analyze this data", {
  toolExecutor: myExecutor,
  maxIterations: 10,
  history: previousMessages,
});

// result: { response, inputTokens, outputTokens, toolCallsCount, iterations, finishReason }

Key types:

  • RunResult -- execution outcome with token counts and finish reason
  • RunOptions -- configuration: toolExecutor, maxIterations, history
  • RunnerProtocol -- interface for custom runner implementations
  • ToolExecutor -- tool execution handler interface
  • AgentLike -- minimal agent shape required by the runner
  • CanonicalMessage / CanonicalMessagePart -- normalized message format
  • convertHistory() -- convert CanonicalMessage[] to CoreMessage[] for the AI SDK

MockRunner

Deterministic runner for testing agents without LLM calls. Pattern-based response routing with tool call simulation.

import { MockRunner } from "@agentic-patterns/runtime";

const mock = new MockRunner()
  .addResponse("analyze", { content: "Revenue up 15%", inputTokens: 10, outputTokens: 20 })
  .addResponse("summarize", {
    content: "Summary complete",
    toolCalls: [{ name: "write_file", arguments: { path: "out.md" }, result: "ok" }],
  })
  .addResponse("*", { content: "Default fallback" }); // wildcard

const result = await mock.run(agent, "analyze Q4");

// Verify calls
mock.callHistory; // [{ message, agentName, model, timestamp }]

// Streaming mode
for await (const event of mock.stream(agent, "analyze")) {
  // yields full event lifecycle: message.start -> tool events -> message.complete
}

// Reset
mock.clear();

Features:

  • Substring trigger matching, * wildcard, auto-fallback
  • Tool call simulation with results
  • Delay and error simulation (delayMs, error fields)
  • Full event lifecycle emission in stream() mode
  • Fluent API and call history recording

ClaudeCodeRunner

Runner backed by the Claude Agent SDK. Delegates to Claude Code's subprocess architecture.

import { ClaudeCodeRunner } from "@agentic-patterns/runtime";

const runner = new ClaudeCodeRunner({
  defaults: { model: "sonnet" },
});

const result = await runner.run(agent, "Fix the bug in auth.ts");

Events (src/events/)

Typed pub/sub event system with discriminated union events.

Event Types: MessageStartEvent, MessageChunkEvent, MessageCompleteEvent, ReasoningEvent, ToolCallIntent, ToolCallRejectedEvent, ToolCallStartEvent, ToolCallEndEvent, IterationStartEvent, IterationEndEvent, LLMCallStartEvent, LLMCallEndEvent, ErrorEvent

All events carry trace fields: traceId, runId, spanId, parentSpanId, timestamp.

Sandbox Events for multi-agent communication: AgentMessageEvent, AgentBroadcastEvent, AgentJoinEvent, AgentLeaveEvent, TaskCreateEvent, TaskUpdateEvent, TaskAssignEvent, HealthPingEvent, HealthPongEvent, NodeLifecycleEvent

import { EventBus, AgentEventBus, EventProfile, subscribeProfile } from "@agentic-patterns/runtime";

const bus = new AgentEventBus();

bus.subscribe("agent.message.complete", (event) => {
  console.log(event.response);
});

subscribeProfile(bus, EventProfile.UX, (event) => {
  // Receives message.start, message.chunk, message.complete, tool events, errors
});

Event Profiles: UX, OBSERVABILITY, DEBUG, TOOLS, STREAMING

Gates (src/gates/)

Gate chain intercepts tool call intents for safety, approval, rate limiting, and auditing.

import { AgentEventBus, SafetyGate, HumanApprovalGate, AuditGate } from "@agentic-patterns/runtime";

const bus = new AgentEventBus();

// Gates are checked in category order: SAFETY -> RATE_LIMIT -> APPROVAL -> AUDIT
bus.addGate(new SafetyGate(["rm", "drop_table"]));
bus.addGate(new HumanApprovalGate(async (event) => {
  return confirm(`Allow ${event.toolName}?`);
}));
bus.addGate(new AuditGate((event) => auditLog.append(event)));

Gate types: SafetyGate, HumanApprovalGate, RateLimitGate, AuditGate

Workflows (src/workflows/)

Composable workflow patterns for multi-step and iterative agent execution: the typed Node layer (below) and the original string-pinned pattern layer (further down).

Typed Node compositions

Everything implements one contract — Node<TIn, TOut> (run(input, ctx){ output, succeeded, error?, totalInputTokens, totalOutputTokens }) — so leaves and composites nest freely: AgentStep (LLM leaf, structured output by default), FunctionStep (deterministic glue), typed Sequential/Parallel/FanOut/Loop/Retry/Accumulate, CoordinatorStep (a model-driven coordinator as a leaf), and the run-scoped Scratchpad slot store.

sequentialAgent

Agents AND nodes in sequence over one implicitly shared Scratchpad. Stage knobs: name / output / prompt / slot / onEmit / stop / reads / writes / retry / input. Per-stage tool executors derive from each agent's own capabilities.

import { type Node, FunctionStep, sequentialAgent } from "@agentic-patterns/runtime";

// Untyped: Node<unknown, SequentialAgentResult> — { outputs, stopped }
const pipeline = sequentialAgent([interpretAgent, { agent: judge, output: Verdict }]);

// Typed: a COMPOSITE-designated emitting stage types the node — the output IS
// that stage's emission, zero casts. input:'prior' hands a node stage the
// previous stage's emission (the compiler-checked spine → tail seam).
const typed: Node<string, Contract> = sequentialAgent<Contract, string>(
  [{ node: coordinatorSpine }, { node: answerTail, input: "prior" }],
  { emit: "answer" }, // type arguments REQUIRE emit; there is no stage-level marker
);
parallelAgent

Fixed, NAMED branches fanned out over a shared input (FanOut remains the tool for dynamic-N over runtime lists). Deterministic index-order join keyed by branch name; leaf-never-throws is lifted into the join — a failed branch becomes a { succeeded: false, error } outcome while the composite still succeeds (check failed.length to hard-fail). Stop policy is complete-all: a branch's stop is a first-in-index-order signal, never a cancellation.

const sections = parallelAgent<{ overview: string; pricing: Pricing }>(
  [
    { agent: overviewDrafter, prompt: (_state, input) => `overview for: ${input}` },
    { agent: pricingDrafter, output: PricingShape },
  ],
  { maxConcurrency: 2 },
);
// → Node<unknown, ParallelAgentResult<…>>: { branches, failed, stopped }

Full semantics (stop lanes, emission slots, build-time race guards) live in the module docs: src/workflows/sequential-agents.ts, src/workflows/parallel-agents.ts.

Scope host (src/workflows/scope-host.ts)

A parsed SessionScope value (@agentic-patterns/core) rides across a run as a SIBLING key on the host bag — RunOptions.host.scope — never inside host.deps (a DepReader; a plain scope object there would crash the first ctx.deps.get()). buildScopeHost(parsed) builds the injection-side fragment; merge it with any other host bits at the call site (the server and CLI wire it in right after scope.parse()):

import { buildScopeHost } from "@agentic-patterns/runtime";

const host = { ...otherHostBits, ...buildScopeHost(parsedScope) };

Reads go through three accessors that all accept BOTH a tool's ToolExecutionContext (ctx.host.scope) and a node's NodeRunContext (ctx.scope directly) — a FunctionStep author and a tool author call the same function:

  • readScope(ctx) — soft probe; undefined when the run carries no scope
  • requireScope(ctx) — fail-loud default read path; throws ScopeUnavailableError with remediation
  • readScopeAs<T>(ctx) — typed cast sugar over readScope, trusting that the server-side scope.parse() already ran; it does NOT re-parse per call
import { requireScope } from "@agentic-patterns/runtime";
import type { ScopeValue } from "@agentic-patterns/core";

const scope = requireScope(ctx) as ScopeValue<typeof myScope>;

Conversation accepts scope the same way — new Conversation(agent, runner, { store, host: buildScopeHost(parsedScope) }). The host bag is fixed at construction and forwarded verbatim into every send()/stream() call for the conversation's lifetime. Scope also survives delegation: a nested AgentStep and an agent invoked as a tool (node-tool.ts, agent-as-tool) both forward the parent's host.scope onto the sub-run's context, so a promoted-node leaf or a delegated sub-agent sees the same bound scope as the top-level run.

AgentRunner and ClaudeCodeRunner both narrow RunOptions.host.scope into the RenderContext passed to agent.renderInitialPrompt(), so scope-derived prompt text (Awareness.fromScope, see @agentic-patterns/core's README) renders on every turn. ClaudeCodeRunner is the one asymmetric case, and it's worth being honest about: scope reaches its rendered system prompt but NOT its tool execution — the Claude Agent SDK's MCP tool loop has no context seam to carry host.scope through, so CC-native and MCP-bridged tools on that runner stay scope-less until that seam exists.

Legacy string-pinned patterns

The original pattern layer. All patterns implement PatternProtocol and share common types.

Base Types

  • PatternContext -- shared context (Record<string, unknown>) threaded through steps
  • MessageTemplate -- static string or (context) => string function
  • Step -- agent + message template + optional output key + context extractor
  • StepResult -- execution result with .content accessor
  • PatternResult -- interface: totalInputTokens, totalOutputTokens, succeeded, finalContent
  • PatternEvent -- discriminated union: start, step.start, step.complete, step.error, iteration.start, iteration.complete, complete
  • PatternHooks -- callbacks for pattern lifecycle events
  • GoalEvaluatorProtocol -- evaluate() returning [achieved, reason, confident]

Helpers: resolveMessage(), makeStepName(), executeStep()

Sequential

Chain agents in sequence, threading context through the pipeline.

import { Sequential } from "@agentic-patterns/runtime";

const pipeline = new Sequential([
  { agent: researcher, messageTemplate: "Research the topic", outputKey: "research" },
  { agent: writer, messageTemplate: (ctx) => `Write about: ${ctx.research}` },
], { continueOnError: false });

const result = await pipeline.run({ topic: "AI" }, { runner });
result.steps;        // StepResult[]
result.finalContext;  // accumulated context

Supports nested patterns (Sequential/Parallel as steps) and continueOnError.

Parallel

Fan-out agents in parallel with optional concurrency limiting and result consolidation.

import { Parallel, collectByName, collectContents } from "@agentic-patterns/runtime";

const fanout = new Parallel(
  [
    { agent: analystA, messageTemplate: "Analyze market", name: "market" },
    { agent: analystB, messageTemplate: "Analyze tech", name: "tech" },
  ],
  { maxConcurrency: 2, consolidator: collectByName },
);

const result = await fanout.run({}, { runner });
result.successful;          // StepResult[]
result.failed;              // [index, Error][]
result.allSucceeded;        // boolean
result.consolidatedOutput;  // { market: "...", tech: "..." }

Built-in consolidators: collectContents (string[]), collectByName (Record). Custom consolidators accepted.

Goal Evaluators

Four implementations of GoalEvaluatorProtocol, ranked cheapest to most expensive:

| Evaluator | Strategy | LLM? | |-----------|----------|------| | SimpleGoalEvaluator | Pattern matching against output | No | | SelfEvalGoalEvaluator | Parses GOAL_STATUS/PROGRESS markers | No | | LLMGoalEvaluator | Sends goal + result to evaluator agent | Yes | | EvaluatorChain | Tries in order, stops on first confident result | Mixed |

All return [achieved: boolean, reason: string, confident: boolean].

import { EvaluatorChain, SimpleGoalEvaluator, LLMGoalEvaluator } from "@agentic-patterns/runtime";

const chain = new EvaluatorChain([
  new SimpleGoalEvaluator({ successPatterns: ["TASK_COMPLETE"] }),
  new LLMGoalEvaluator({ agent: evaluatorAgent, runner }),
]);

TaskLoop

Goal-driven iteration: run agent, evaluate progress, repeat.

import { TaskLoop, SimpleGoalEvaluator } from "@agentic-patterns/runtime";

const loop = new TaskLoop(agent, new SimpleGoalEvaluator({
  successPatterns: ["TASK_COMPLETE"],
  failurePatterns: ["CANNOT_PROCEED"],
}), { maxIterations: 5 });

const result = await loop.run("Fix all failing tests", {}, { runner });
result.exitReason;  // "goal_achieved" | "max_iterations" | "explicit_stop" | "error"
result.iterations;  // number of iterations executed

Features: history summarization in prompts, configurable stop phrases, goal evaluation per iteration.

EvaluatorLoop

Producer-evaluator refinement: producer generates, evaluator scores + critiques, producer refines.

import { EvaluatorLoop, RubricEvaluator, CompositeRefinementEvaluator } from "@agentic-patterns/runtime";

const rubric = new RubricEvaluator([
  { name: "clarity", description: "Clear and concise", weight: 0.4 },
  { name: "accuracy", description: "Factually correct", weight: 0.6 },
], { runner });

const loop = new EvaluatorLoop(producer, rubric, {
  maxRefinements: 3,
  qualityThreshold: 0.8,
});

const result = await loop.run("Write a blog post about RAG");
result.exitReason;  // "quality_met" | "max_refinements" | "no_improvement" | "error"
result.bestOutput;  // highest-scoring version across all refinements

Evaluator implementations: LLMRefinementEvaluator, RubricEvaluator, CompositeRefinementEvaluator (weighted average).

RetryLoop

Generic async retry wrapper. Not agent-specific -- wraps any () => Promise<T>.

import { RetryLoop, ExponentialBackoff, JitteredBackoff, FixedBackoff } from "@agentic-patterns/runtime";

const retry = new RetryLoop({
  maxAttempts: 5,
  backoff: new ExponentialBackoff({ initialMs: 100, maxMs: 5000 }),
  retryableErrors: [RateLimitError],
  timeoutMs: 30_000,
  onRetry: (attempt, error) => console.log(`Retry ${attempt}: ${error.message}`),
});

const result = await retry.run(() => callExternalAPI());

Backoff strategies: FixedBackoff, ExponentialBackoff, JitteredBackoff.

ConversationLoop

Multi-turn conversation orchestration with external input/output callbacks.

import { ConversationLoop } from "@agentic-patterns/runtime";

const loop = new ConversationLoop(agent, {
  maxExchanges: 10,
  exitPhrases: ["goodbye", "exit"],
  inputFn: async () => getUserInput(),
  outputFn: async (response) => displayToUser(response),
});

const result = await loop.run({ runner });
result.exitReason;     // "exit_phrase" | "max_exchanges" | "error"
result.exchangeCount;  // number of exchanges completed

Integrates with ConversationStore for persistence via InMemoryConversationStore.

Transport (src/transport/)

Message transport for multi-agent communication.

import { InProcessTransport, MessagingToolbox } from "@agentic-patterns/runtime";

const transport = new InProcessTransport();
transport.subscribe("agency.*.messages", (msg) => { /* ... */ });
await transport.publish("agency.worker.messages", payload);

const toolbox = new MessagingToolbox(transport, senderAddress, agency);

Transport interface can be implemented for external systems (NATS, Redis, etc.).

Runtime (src/runtime/)

Multi-agent execution runtime.

import { Agency } from "@agentic-patterns/core";
import { AgencyRuntime, AgentNode } from "@agentic-patterns/runtime";

const runtime = new AgencyRuntime(agency, runner, "run-123");
await runtime.start();
await runtime.injectCoordinator("Begin processing");
const status = runtime.status(); // { coordinator: "running", worker: "running" }
await runtime.stop();

AgentNode wraps a single agent with message batching, idle timeout, max turns, and lifecycle events.

Conversation (src/conversation/)

Conversation state management with structured persistence.

import { Conversation, InMemoryConversationStore } from "@agentic-patterns/runtime";

// In-memory persistence
const store = new InMemoryConversationStore();

const convo = new Conversation(agent, runner, { id: "conv-123", store });
const exchange = await convo.send("Hello!");
console.log(exchange.assistant);

ConversationStore provides full CRUD for conversations, messages, and message parts:

  • createConversation(), getConversation(), updateConversation()
  • addMessage(), getMessages(), getMessageParts()

InMemoryConversationStore is the built-in in-memory implementation. Implement ConversationStore for database-backed persistence.

Exporters (src/exporters/)

Observability exporters that subscribe to EventBus events.

| Exporter | Output | |----------|--------| | ConsoleExporter | Terminal output via configurable logger | | LangfuseExporter | Langfuse trace spans | | OTelExporter | OpenTelemetry trace spans |

import { ConsoleExporter, createConsoleExporter } from "@agentic-patterns/runtime";

const exporter = createConsoleExporter(bus);
exporter.start();
// ... run agent ...
exporter.stop();

All exporters extend BaseExporter which manages EventBus profile subscription lifecycle.

Presets (src/presets/)

Pre-built roles, judgments, and responsibilities for common agent patterns.

Roles:

  • coordinatorRole(name, persona) -- orchestration coordinator
  • orchestratorRole(name, persona) -- task orchestrator
  • analystRole(name, persona) -- research/analysis specialist
  • retrievalRole(name, persona) -- information retrieval specialist

Judgments: ROUTING, QUALITY_REVIEW, INTENT_CLASSIFICATION, RETRIEVAL_STRATEGY, EVIDENCE_QUALITY

Responsibilities: ORCHESTRATION, QUALITY_GATE, INTENT_ROUTING, RESPONSE_SYNTHESIS, INFORMATION_RETRIEVAL, ANALYSIS

import { coordinatorRole, ROUTING, ORCHESTRATION } from "@agentic-patterns/runtime";

const role = coordinatorRole("lead", persona);

No model defaults (#179/#222). No preset — role or demo agent — pins a defaultModel. A framework DECLARATION never names a vendor's model: you would inherit a model you never chose, on a provider you may not use, and introspection (the playground's Roles page) would render it as THE model. Set the model where you compose the agent (AgentBuilder.withModel(id) / your own role.withDefaultModel(id)), or let the runner resolve it from the environment (AGENT_TIER / AGENT_MODEL, a gateway, or models.yaml profiles). Resolving a model from the ENVIRONMENT at run time is the supported mechanism; pinning one in a declaration is not. An agent that declares no model on a resolver-backed runner fails loud rather than defaulting to a vendor.