LetspingLetsPing
← Home/Documentation
Overview

LetsPing

LetsPing is the seatbelt for high-stakes autonomous agents. Wrap the .tool() calls that run destructive actions (POST, PUT, DELETE). We apply a deterministic firewall, then a Markov-based behavioral lens to surface unusual sequences. The system learns from every approval and, optionally, issues agent credentials and escrow so agents can be accepted as first-class customers.

State persisted, not held in memory
Your process is free to exit. The pending request lives in LetsPing's Postgres backend. When a human resolves it, the SDK's polling loop picks up the result on the next check (exponential backoff, 1s → 10s).
API key auth
All requests require a Bearer token ( LETSPING_API_KEY). Keys are generated in the dashboard and scoped to a project.
RLHF Decay (Payload Patching)
The operator can change field values before approving. The SDK explicitly computes a diff_summary and returns APPROVED_WITH_MODIFICATIONS. Your agent learns from the correction and stops repeating the mistake.
Mobile companion app
Pair your phone via QR code from the dashboard. Incoming requests appear as swipeable cards — approve or reject from your pocket.

Security & Compliance

Definitive architectural guides for teams deploying autonomous agents to production. For shared responsibility, DPA, and vendor questionnaires, see Trust & Compliance.

Core Engine

The Autonomous Shield

1. Markov Behavioral Profiling (Versioned)

Every time an agent executes a tool, LetsPing hashes the parameter schema and structural context (the skeleton_hash). Over time, this builds a baselined graph of your agent's reasoning pathways. Each transition is scored by edge anomaly score; when a score exceeds mean + 3·σ over the learned history (or a fallback threshold), the Shield detects a high-entropy reasoning anomaly and intervenes.

2. Shadow Mode and baseline locking

LetsPing starts every agent in Shadow Mode. The engine observes your agent without pausing execution and builds a Markov baseline of normal paths. After enough stable runs the status bar in the dashboard progresses to Soft-Lock, where anomalies are detected and surfaced as metrics, and finally to Enforcing, where anomalous transitions can pause requests for human review before they reach your tools.

3. Live Execution Graph

The LetsPing dashboard includes a real-time node map. You can visually trace the blast radius of hallucinations, identifying exactly where the agent's logic drifted from the baseline before it hit your protected tool wrapper.

4. Approval-as-Learning

Human approvals aren't just temporary overrides. When you approve a novel payload, LetsPing injects weight into that new Markov path. To handle concept drift, unapproved legacy pathways experience Time Decay, slowly fading from the baseline graph until they are flagged as anomalies again.

Getting Started

Quickstart

1
Install
npm install @letsping/sdk
2
Set your API key

Create a project at letsping.co → Settings → Developers. Copy the generated key.

# .env
LETSPING_API_KEY=lp_live_...
3
Call ask() or tool()

Wrapping critical tools also activates automatic behavioral profiling across your agent's execution graph.

agent.ts
import { LetsPing } from "@letsping/sdk";

const lp = new LetsPing();

// Execution halts here until a human approves or rejects.
const decision = await lp.ask({
  service:  "billing-agent",
  action:   "refund_user",
  priority: "high",           // "low" | "medium" | "high" | "critical"
  payload:  { user_id: "u_123", amount: 550, currency: "usd" }
});

if (decision.status === "APPROVED") {
  // Action authorized with no modifications.
  await refund(decision.payload);
} else if (decision.status === "APPROVED_WITH_MODIFICATIONS") {
  // Operator edited your payload. Learn from the diff_summary!
  console.log("Corrections:", decision.diff_summary);
  await refund(decision.patched_payload);
} else {
  // Status is "REJECTED" — stop here.
  console.log("Rejected:", decision.metadata);
}
Reference

API Reference

Wrapping critical tools also activates automatic behavioral profiling across your agent's execution graph.

.ask(options: RequestOptions): Promise<Decision>
Blocking. Submits the request and polls until a human resolves it (up to timeoutMs, default 24 h). Returns a Decision object with status ("APPROVED" or "REJECTED"), the original payload, and patched_payload if the operator edited values. Python raises ApprovalRejectedError on rejection.
.defer(options: RequestOptions): Promise<{ id: string }>
Non-blocking. Submits the request and returns the request id immediately. Use this in serverless functions or queues where you can't hold a connection open. Poll GET /api/status/:id or configure a webhook to receive the result.
.tool(service, action, priority?): (context) => Promise<string>
Returns a callable that wraps ask() in the shape expected by OpenAI function calling or similar tool conventions. Takes a string or object as context, returns an "APPROVED" / "STOP" / "ERROR" string the LLM can interpret.
.LetsPingCheckpointernew LetsPingCheckpointer(lp: LetsPing)
A native LangGraph BaseCheckpointSaver. Automatically encrypts and persists thread state remotely to bypass serverless memory limits. Drop this into your StateGraph.compile() options.

RequestOptions fields

FieldTypeDescription
servicestringName of the agent or service (e.g. "billing-agent")
actionstringSpecific action being requested (e.g. "refund_user")
payloadobjectThe data the human will see and optionally edit
priority"low"|"medium"|"high"|"critical"Urgency. Affects visual treatment in the dashboard. Default: "medium"
schemaZod schema | JSON SchemaOptional. Generates a typed edit form in the dashboard for payload patching
timeoutMsnumberOptional. Max wait in ms. Default: 86,400,000 (24 h)
rolestringOptional. Routes to a specific team role (e.g. "finance")

Agent Credentials

Provision agent_id and agent_secret via POST /api/agents/register (auth: Bearer project API key). Rotate with POST /api/agents/rotate (body: { "agent_id": "..." }). In the agent process, set LETS_PING_AGENT_ID and LETS_PING_AGENT_SECRET (or pass from your secret store).

For a full tour of agent-first endpoints (attestation, proof, registry, trust score, billing), see the Agent APIs reference.

Agent Credentials & Escrow

In addition to wrapping tools with ask(), agents can obtain first-class credentials and sign their own ingest calls. Use the Agent Credentials API to provision an agent_id and agent_secret per agent, then attach signatures and escrow envelopes using the SDK helpers.

// 1. Provision a credential (from your backend)
const res = await fetch("https://letsping.co/api/agents/register", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.LETSPING_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ agent_name: "billing-refunder", framework: "langgraph", environment: "prod" }),
});
const { agent_id, agent_secret } = await res.json();

// 2. Inside the agent process, sign ingest bodies
import { signIngestBody } from "@letsping/sdk";

const body = {
  project_id: process.env.LETSPING_PROJECT_ID!,
  service: "billing-agent",
  action: "refund_user",
  payload: { user_id: "u_123", amount: 100 },
};

const signed = signIngestBody(agent_id, agent_secret, body);
await fetch("https://letsping.co/api/ingest", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.LETSPING_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify(signed),
});

For multi-agent payments or AP2/x402-style mandates, include mandate payloads under _escrow.x402_mandate / _escrow.ap2_mandate and verify them via the Agent Escrow Spec at /docs/agent-escrow-spec.

Accepting agents as customers

SaaS and storefronts can accept agents as first-class customers by verifying LetsPing webhooks and escrow envelopes. See the guide: How to accept agents as customers (Visa for Agents).

Feature

Payload Patching

When you pass a schema to ask(), the dashboard renders a type-safe form pre-filled with the submitted payload values. The operator can change any field before approving. The SDK explicitly returns an APPROVED_WITH_MODIFICATIONS status alongside a structural diff_summary, enabling your agent to learn from its mistakes and prevent future alerts (RLHF).

agent.ts
import { LetsPing } from "@letsping/sdk";
import { z } from "zod";

const lp = new LetsPing();

// Pass a Zod schema and the dashboard renders a type-safe edit form.
// The operator can change values before approving.
const decision = await lp.ask({
  service: "outreach-bot",
  action:  "send_email",
  payload: { to: "ceo@acme.com", subject: "Q1 recap", body: "..." },
  schema: z.object({
    to:      z.string().email(),
    subject: z.string(),
    body:    z.string().describe("Plain text or Markdown"),
  })
});

// Check if the operator edited the subject.
const final = decision.status === "APPROVED_WITH_MODIFICATIONS" 
  ? decision.patched_payload 
  : decision.payload;
Without a schema, the dashboard shows the raw JSON payload as read-only. The operator can still approve or reject, but cannot edit values.
Security

Payload Encryption

LetsPing employs Zero-Plaintext Storage by default. All payloads are automatically encrypted at the application layer using an AES-256 master key before being persisted to the backend. For organizations requiring strict zero-trust guarantees, you can also enable Client-Side E2E Encryption. This ensures LetsPing servers never even see the plaintext values in transit.

1
Generate your E2E key

Open Settings → Encryption in the dashboard. Click Generate key. A 256-bit AES key is created in your browser using the Web Crypto API and saved to localStorage. Copy the key — it is shown only once.

2
Set the environment variable
# .env — same file as your API key
LETSPING_API_KEY=lp_live_...
LETSPING_ENCRYPTION_KEY=<paste key here>
The SDK reads LETSPING_ENCRYPTION_KEY from the environment automatically. No constructor changes needed — your existing lp.ask() calls start encrypting immediately.
3
Verify in the dashboard

Submit a request. In the triage queue, open it — the payload panel shows a Decrypted badge and the plaintext values, decrypted locally using the key in your browser's localStorage. If you open the dashboard on a different browser or device, you will see a "key not loaded" notice instead. Import the key from Settings → Encryption on that device.

What LetsPing stores
The payload column contains { _lp_enc: true, iv: "...", ct: "..." }. There is no server-side key — LetsPing cannot decrypt it, even with full database access.
Patching encrypted payloads
When you approve a request with edits, the dashboard re-encrypts the modified values before sending them to /api/resolve. The SDK decrypts patched_payloadon the way out, so your agent code sees plain objects — not ciphertext.
Opt-in E2E Encryption
Requests submitted without a client-side LETSPING_ENCRYPTION_KEY set are secured by server-side envelope encryption automatically and remain completely backward compatible. The API generates a unique Data Encryption Key (DEK) for every request to guarantee perfect forward secrecy.
Direct-to-Storage State Hydration
To bypass serverless timeout limits, the SDK optionally accepts a state_snapshot. The SDK encrypts this massive state payload symmetrically (either via your E2E Client Key or the auto-generated Server Fallback Envelope keys) and PUTs it directly to Supabase storage. Pointers are stored, but the database is never bloated with huge JSON context windows.
Security

Guardrails

LetsPing ships a deterministic guardrail engine that fires on every ingest request — no training period required. Rules run before the request reaches your approval queue, so bad payloads never surface to humans.

DLP — PII & Secret Redaction

Scans every text field for SSNs, payment card numbers, IBANs, JWTs, bearer tokens, API keys, PEM blocks, and email addresses. Matching values are replaced with a redacted placeholder before the payload is stored or forwarded. Your audit logs never contain raw credentials.

SSN / IBANPayment cardsJWTs & API keysPEM blocks

Prompt Injection Blocking

Normalizes all input (Unicode NFKD, bidirectional character removal, leet-speak expansion) then matches against a curated rule set covering direct override attempts, roleplay jailbreaks, delimiter tricks, indirect RAG injection, turn manipulation, and token smuggling. Obfuscated bypasses like "ign0re all prev10us" are caught.

Jailbreak patternsLeet-speak normalizationRAG injectionToken smuggling

SSRF & Egress Firewall

Deep-scans the entire payload tree (including nested tool arguments) for URL-like strings. Rejects private RFC-1918 ranges, link-local addresses (169.254.x.x), metadata IPs, and wildcard DNS targets across IPv4 and IPv6. Handles hexadecimal, integer, and octal IP encodings used in common SSRF bypass attempts. An allowlist lets you permit specific external domains.

169.254.x.x blockedHex/octal IP encodingDeep payload scanAllowlist support

Velocity & Rate Limiting

Tracks requests per agent/service over a sliding window using Redis. Configurable threshold and window. Exceeding the limit blocks future requests for the remainder of the window and surfaces the agent in the anomaly dashboard.

Sliding windowPer-agent trackingConfigurable threshold

Cost Guard

Estimates token count using a bytes/3.5 model and rejects requests that would exceed your configured token or cost budget. Prevents runaway LLM loops from accumulating unbounded spend before a human can intervene.

Token estimationBudget capLoop spend protection

Semantic Loop Detection

Hashes each outbound payload and maintains a rolling window in Redis. When an agent submits semantically identical requests consecutively (configurable threshold), the loop is detected and blocked. Prevents hallucination loops from hammering your tools indefinitely.

Payload hashingConsecutive duplicate detectionConfigurable window

Markov Behavioral Profiling

Builds a SHA-256 behavioral graph of your agent's tool-call sequences over time. Each transition is scored against mean ± 3σ of the learned history. Novel paths in Shadow Mode surface as metrics; in Enforce Mode they pause execution for human review. Approvals reinforce the graph; unused paths decay over time.

SHA-256 graphShadow / Soft-Lock / EnforcingTime decayApproval learning
Agent Economy

Agent Economy

LetsPing is the trust layer for the machine economy. Beyond human-in-the-loop approvals, agents can hold identities, attest to their capabilities, earn trust scores, and exchange value through escrow contracts — all without a human intermediary.

Trust Tiers (0–3)
Every agent has a trust score (0–1000) that maps to a tier. Tier 0 agents can handle tasks up to $10. Tier 3 agents (score 800–1000) unlock up to $100,000 bounties. Score is computed from approval rate, dispute history, and verified attestations.
Escrow Contracts
Agents can attach typed commitments to handoffs: pay_on_delivery, revenue_split, lead_gen_bounty, bug_bounty, and more. All amounts must be positive whole cents, denominated in an ISO 4217 currency, and within the agent's tier cap. Revenue splits are validated to sum to exactly 1.0 (±0.01).
Machine-Readable Descriptor
GET /agent/descriptor returns a JSON document describing all agent-economy capabilities, trust tier bounds, escrow contract schemas, signed call pricing, and supported currencies — formatted for autonomous agent discovery via x402 and AP2 protocols.
Atomic Usage Enforcement
Agent economy usage (signed calls, escrow handoffs) is enforced via atomic Lua scripts in Redis. Concurrent workers cannot race past plan limits — the TOCTOU vulnerability common in naive billing implementations is eliminated.
Integrations

Framework Adapters

The @letsping/adapters package wraps ask() in the tool shape expected by each framework so you don't have to write glue code.

LangChain / LangGraph

createLetsPingTool returns a DynamicStructuredTool. Drop it into any AgentExecutor or LangGraph node.

agent.ts
import { createLetsPingTool } from "@letsping/adapters/langchain";
import { z } from "zod";

// Drop into any LangGraph agent or standard LangChain AgentExecutor.
// The LLM calls this tool; execution pauses until a human resolves it.
const tools = [
  createLetsPingTool({
    name:        "deploy_to_prod",
    description: "Deploys the current build to production. Requires human sign-off.",
    priority:    "critical",
    schema: z.object({
      version:     z.string().describe("Semver tag to deploy, e.g. v1.4.2"),
      environment: z.enum(["staging", "production"]),
    })
  })
];

Vercel AI SDK

letsPing() wraps createVercelTool from the ai package. Use inside a Next.js Route Handler with streamText.

route.ts
import { letsPing } from "@letsping/adapters/vercel";
import { z } from "zod";

// In a Next.js Route Handler using Vercel AI SDK streamText:
const tools = {
  refund_user: letsPing({
    name:        "refund_user",
    description: "Issues a refund. Requires human approval before execution.",
    priority:    "high",
    schema: z.object({
      user_id: z.string(),
      amount:  z.number().positive(),
    })
  })
};
Integrations

MCP Server

@letsping/mcp is an MCP server that exposes a single tool: ask_human. Install it in Claude Desktop or Cursor and your agent can call it the same way it calls any other tool. No code required.

terminal
# Start the MCP server (reads LETSPING_API_KEY from env)
npx @letsping/mcp

# Claude Desktop — add to claude_desktop_config.json:
{
  "mcpServers": {
    "letsping": {
      "command": "npx",
      "args": ["@letsping/mcp"],
      "env": { "LETSPING_API_KEY": "lp_live_..." }
    }
  }
}

# Cursor — add to .cursor/mcp.json with the same structure.
# The agent now has access to the "ask_human" tool.
The MCP tool accepts the same parameters as ask():service, action, payload, priority, role, and timeout. On rejection, it returns a text string starting with ACTION_REJECTED:so the LLM can gracefully stop the task.
Platform

Mobile Companion

The mobile app is a PWA — add letsping.co/mobile to your home screen (iOS or Android). Pair your phone once by scanning a QR code from the dashboard. After that, incoming pending requests appear as cards you can swipe to approve or tap to reject. No separate app store install.

Pairing
Dashboard → Mobile icon → scan QR code. The pairing is project-scoped and stored in a secure browser session.
Approval flow
Each pending request shows the service name, action, priority level, and a preview of the payload fields. Swipe right to approve, tap Reject to block with an optional reason.
OpenClaw skill
If you use Claude Desktop or Cursor, the OpenClaw skill (git clone) wires up the MCP server and mobile companion into your agent workspace without manual configuration.
Platform

Webhooks

Configure a webhook URL in Settings → Developers → Webhooks. LetsPing will POST to your endpoint when a request is created, approved, or rejected. Use this to trigger downstream logic when you're usingdefer() or any other async flow.

webhook-payload.json
// LetsPing sends a POST to your endpoint on every status change.
// Payload shape:
{
  "event":      "request.approved",  // or "request.rejected" | "request.created"
  "request_id": "req_abc123",
  "status":     "APPROVED",
  "service":    "billing-agent",
  "action":     "refund_user",
  "payload":    { "user_id": "u_123", "amount": 550 },
  "patched_payload": { "user_id": "u_123", "amount": 220 }, // if operator edited
  "resolved_at": "2026-02-20T19:30:00Z",
  "state_download_url": "https://[project].supabase.co/storage/v1/object/sign/agent_states/states/req_abc123.enc?token=..."
}
Check patched_payload first — if the operator edited values, it will be present. Fall back to payload if not.

Handling State Rehydration

LetsPing does not magically inject state back into your framework natively, as LangGraph and Vercel AI manage memory differently. You must use the state_download_url provided in the webhook to fetch the frozen state, decrypt it, and manually trigger your agent's resume logic.

route.ts
// app/api/webhook/letsping/route.ts
import { NextResponse } from "next/server";
import { LetsPing } from "@letsping/sdk";

const lp = new LetsPing(); // Automatically loads LETSPING_ENCRYPTION_KEY if set

export async function POST(req: Request) {
  const body = await req.json();
  
  if (body.status === "APPROVED" || body.status === "APPROVED_WITH_MODIFICATIONS") {
      const finalPayload = body.patched_payload || body.payload;
      
      let hydratedState = null;
      if (body.state_download_url) {
          // 1. Fetch the frozen Cryo-Sleep state
          const res = await fetch(body.state_download_url);
          const encryptedState = await res.json();
          
          // 2. Decrypt locally
          hydratedState = (lp as any)._decrypt(encryptedState);
      }
      
      // 3. Manually resume your specific framework (e.g. LangGraph)
      // await resumeAgent(hydratedState, finalPayload);
  }
  
  return NextResponse.json({ success: true });
}