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-gate

v1.4.0

Published

Deterministic schema validation gates and circuit breakers for LLM function calling

Readme

🛡️ Agentic Deterministic State Engine

A production-ready, model-agnostic proxy engine that enforces strict runtime execution bounds, schema validation gates, and self-correction loops for LLM Function Calling.

npm version npm downloads Node.js AWS Bedrock Zod License: MIT


📌 Problem Statement

LLM Agentic Function Calling (Tools) is inherently non-deterministic:

  • Hallucinated Arguments: Models frequently output invalid parameters (e.g., non-existent regions, malformed UUIDs, out-of-bound dates).
  • Infinite Loops: When a tool call fails, agents often re-issue the exact same broken payload indefinitely.
  • Unsafe Execution: Executing unvalidated tool calls directly against downstream infrastructure (AWS, SQL databases, payment gateways) risks severe operational failure.

💡 Solution Architecture

This repository implements a Deterministic State Engine sitting between the user/application and downstream infrastructure tools. It intercepts every tool invocation requested by the LLM, passes it through a local Zod Schema Validation Gate, and manages a stateful retry loop with error feedback injection.

                      ┌─────────────────────────────────┐
                      │    User Input / System Prompt   │
                      └────────────────┬────────────────┘
                                       │
                                       ▼
                      ┌─────────────────────────────────┐
                      │   LLM Provider (Bedrock/OpenAI) │
                      └────────────────┬────────────────┘
                                       │
                                       ▼  (Tool Use Requested)
                      ┌─────────────────────────────────┐
                      │    DETERMINISTIC STATE ENGINE   │
                      │                                 │
                      │   ┌─────────────────────────┐   │
                      │   │  Zod Validation Gate    │   │
                      │   └────────────┬────────────┘   │
                      └────────────────┼────────────────┘
                                       │
                     ┌─────────────────┴─────────────────┐
                     │                                   │
             [Schema PASSED]                     [Schema FAILED]
                     │                                   │
                     ▼                                   ▼
        ┌─────────────────────────┐         ┌─────────────────────────┐
        │  Execute Downstream API │         │ Inject Error Feedback   │
        │  (AWS EC2, DB, etc.)    │         │ into Message History    │
        ┌─────────────────────────┐         └────────────┬────────────┘
                     │                                   │
                     ▼                                   ▼
        ┌─────────────────────────┐         ┌─────────────────────────┐
        │     Return Success      │         │ Loop (Max Retries Check)│
        └─────────────────────────┘         └─────────────────────────┘
flowchart TD
    A[LLM Provider<br/>Bedrock / OpenAI / Anthropic / Gemini] -->|Tool Use Requested| B[Interceptor Gate]
    B --> C{Zod Schema<br/>Validation}
    C -->|PASSED| D[Execute Downstream Tool<br/>AWS API / DB / Payment Gateway]
    C -->|FAILED| E[Inject Error Feedback<br/>into Message History]
    E -->|Retry within maxRetries| A
    D --> F[Return Result to Caller]

Key Features

  • 🛡️ Zero-Trust Validation Gate: Uses Zod schemas to intercept and validate parameters locally before hitting downstream APIs.
  • 🔄 Self-Correction Feedback Loop: Feeds exact schema validation error traces back into the conversation history, allowing the LLM to self-correct in subsequent attempts.
  • Circuit Breaker: After maxConsecutiveFailures (default 3) failures in a row for the same tool, the gate trips and rejects further calls before touching the schema or execute() — stopping runaway LLM retry loops without your own bookkeeping. Reset with gate.resetCircuit(toolName).
  • 📡 Telemetry Hooks: onGateSuccess / onGateFailure callbacks fire on every call, so you can export metrics to OpenTelemetry, Datadog, CloudWatch, or plain logs.
  • 🔍 Async External-State Validation: Add an optional validate() hook per tool to check real external state (e.g. does this EC2 instance ID actually exist) before execute() runs — a rejected validate() counts as a gate failure just like a schema mismatch.
  • 🔌 Provider-Agnostic Design: Decoupled architecture support for AWS Bedrock, OpenAI, Anthropic, and Google Gemini.
  • 🔑 Zero Secrets Leakage: Native integration with local AWS credentials (aws configure) or environment variables.

🏗️ Engine Sequence Diagram

User               Engine             Zod Gate           Bedrock / LLM         AWS API
 │                    │                   │                    │                  │
 │─ Send Prompt ─────>│                   │                    │                  │
 │                    │── Converse API ───────────────────────>│                  │
 │                    │<── Tool Request ("restart_ec2") ───────│                  │
 │                    │                   │                    │                  │
 │                    │── Validate Input >│                    │                  │
 │                    │<─ FAILED (Region)─│                    │                  │
 │                    │                   │                    │                  │
 │                    │── Append Tool Error & Retry Loop ─────>│                  │
 │                    │<── Self-Corrected Text Response ───────│                  │
 │                    │                   │                    │                  │
 │<─ Return Result ───│                   │                    │                  │

🛠️ Multi-AI Provider Extensibility

While the core implementation uses AWS Bedrock Converse API, the state engine and validation gates are completely provider-agnostic.

Below is how the exact same validation engine wraps other AI providers:

1. AWS Bedrock (Default Implementation)

import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";

const response = await bedrockClient.send(new ConverseCommand({
  modelId: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
  messages: messages,
  toolConfig: toolConfig
}));

2. OpenAI (openai Package)

import OpenAI from "openai";
const openai = new OpenAI();

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: messages,
  tools: toolsSpec
});

const toolCall = response.choices[0].message.tool_calls?.[0];
const validation = EC2RestartSchema.safeParse(JSON.parse(toolCall.function.arguments));

3. Anthropic Native (@anthropic-ai/sdk)

import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();

const response = await anthropic.messages.create({
  model: "claude-3-5-sonnet-20241022",
  messages: messages,
  tools: toolsSpec
});

const toolCall = response.content.find(c => c.type === "tool_use");
const validation = EC2RestartSchema.safeParse(toolCall.input);

4. Google Gemini (@google/genai)

import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

const response = await ai.models.generateContent({
  model: "gemini-3.6-flash",
  contents,
  config: { tools: [{ functionDeclarations: [restartEc2Declaration] }] }
});

const call = response.functionCalls[0];
const validation = EC2RestartSchema.safeParse(call.args);

🚀 Quick Start

1. Install

npm install agentic-gate zod

2. Register a tool and intercept a call

This is the core API — provider-agnostic, no AWS/network calls required:

import { AgenticGate } from "agentic-gate";
import { z } from "zod";

const gate = new AgenticGate();

gate.registerTool({
  name: "restart_ec2_instance",
  schema: z.object({
    instanceId: z.string().regex(/^i-[a-f0-9]{8,17}$/, "Invalid AWS EC2 Instance ID format"),
    region: z.enum(["us-east-1", "us-west-2", "ap-south-1"]),
  }),
  execute: async ({ instanceId, region }) => {
    // Your real downstream call (AWS SDK, DB, HTTP, etc.)
    return { status: "success", instanceId, region };
  },
});

// Feed it raw, untrusted arguments straight from the LLM's tool-call payload
const result = await gate.interceptAndExecute("restart_ec2_instance", {
  instanceId: "i-0123456789abcdef0",
  region: "eu-central-1", // not in the enum -> gate rejects before execute() runs
});

if (!result.success) {
  console.log(result.error);
  // "[Validation Gate Failed]: region: Invalid enum value..."
  // Feed this string back into the LLM's message history so it can self-correct.
}

3. Circuit breaker + telemetry

Configure both when constructing the gate:

const gate = new AgenticGate({
  maxConsecutiveFailures: 3, // 0 disables the breaker
  onGateFailure: (e) => metrics.increment(`gate.failure.${e.reason}`, { tool: e.toolName }),
  onGateSuccess: (e) => metrics.increment("gate.success", { tool: e.toolName }),
});

// After 3 consecutive failures for "restart_ec2_instance", the gate short-circuits:
// { success: false, error: "[Circuit Breaker OPEN]: Tool 'restart_ec2_instance' has failed 3 consecutive times..." }

gate.resetCircuit("restart_ec2_instance"); // once the underlying issue is fixed

4. Async external-state validation

Zod only checks the shape of the arguments — it can't tell you whether i-0123456789abcdef0 is an EC2 instance that actually exists. For that, add a validate() hook: it runs after the schema passes and before execute(), and throwing rejects the call exactly like a schema failure (same circuit breaker, same telemetry, reason "async-validation"):

import { EC2Client, DescribeInstancesCommand } from "@aws-sdk/client-ec2";
const ec2 = new EC2Client({});

gate.registerTool({
  name: "restart_ec2_instance",
  schema: z.object({
    instanceId: z.string().regex(/^i-[a-f0-9]{8,17}$/),
    region: z.enum(["us-east-1", "us-west-2", "ap-south-1"]),
  }),
  validate: async ({ instanceId, region }) => {
    const { Reservations } = await ec2.send(
      new DescribeInstancesCommand({ InstanceIds: [instanceId] }, { region })
    );
    if (!Reservations?.length) {
      throw new Error(`Instance '${instanceId}' does not exist in ${region}`);
    }
  },
  execute: async ({ instanceId, region }) => {
    // Safe to restart — we already confirmed the instance is real.
  },
});

5. Less boilerplate with an adapter

Every provider example above ends up writing the same ~30 lines: pull the tool call(s) out of the response, parse the arguments, run the gate, and rebuild a provider-shaped result message. agentic-gate/adapters/* does that for you — one adapter per provider, since each has its own response and message shape:

import { AgenticGate } from "agentic-gate";
import { handleResponse } from "agentic-gate/adapters/openai"; // or /anthropic, /bedrock, /gemini

for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
  const response = await openai.chat.completions.create({ model, messages, tools });
  const outcome = await handleResponse(gate, response);
  messages.push(...outcome.messages); // ready-to-append message(s), gate already applied

  if (outcome.done) {
    console.log(outcome.text);
    break;
  }
}

Each adapter only depends on the shape of its provider's response object — not the provider's SDK — so none of them add a dependency. All four handle multiple simultaneous tool calls in one response correctly (Anthropic/Bedrock bundle every result into a single following turn, as required).

6. See it wired into a real provider loop

The snippet above validates a single call. For the full retry loop — sending the validation error back to the model and looping until it self-corrects or hits maxRetries — see the runnable examples in examples/. Each provider has both a raw version (manual parsing, to see the mechanics) and an -adapter version (using agentic-gate/adapters/*, to see the boilerplate disappear):

| Example | Provider | Domain | |---|---|---| | examples/bedrock-converse.mjs / -adapter | AWS Bedrock Converse API | EC2 instance restart | | examples/openai.mjs / -adapter | OpenAI Function Calling | EC2 instance restart | | examples/anthropic.mjs / -adapter | Anthropic Messages API | EC2 instance restart | | examples/gemini.mjs / -adapter | Google Gemini API | Satellite launch scheduling (non-infra) | | examples/pizza-order.mjs | AWS Bedrock Converse API | Pizza ordering (non-infra, to show the gate isn't AWS-specific) | | examples/langchain.mjs | LangChain (@langchain/google-genai) | Pet adoption — gate wrapped inside a LangChain tool() handler |

Each example requires only its provider's SDK and credentials — see examples/README.md for setup.


🧪 Real Execution Logs

Here is the deterministic execution output when asking the LLM to restart an instance in an unsupported region (eu-central-1):

🚀 Starting Engine Execution for Prompt: "Please restart instance i-0123456789abcdef0 in Frankfurt (eu-central-1)"
📡 Using Bedrock Model: us.anthropic.claude-haiku-4-5-20251001-v1:0

--- Loop Attempt 1/3 ---
[Bedrock Tool Requested]: "restart_ec2_instance" with input: { instanceId: 'i-0123456789abcdef0', region: 'eu-central-1' }
❌ [Validation Gate FAILED]: Region must be one of: us-east-1, us-west-2, ap-south-1

--- Loop Attempt 2/3 ---

🤖 [Model Feedback]: I apologize, but it appears that the restart function is currently only available for the following AWS regions:
- us-east-1 (US East - N. Virginia)
- us-west-2 (US West - Oregon)
- ap-south-1 (Asia Pacific - Mumbai)

The Frankfurt region (eu-central-1) is not supported. To restart your instance in Frankfurt, you would need to use the AWS Console directly.

📄 License

This project is licensed under the MIT License.