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.
LETSPING_API_KEY). Keys are generated in the dashboard and scoped to a project.diff_summary and returns APPROVED_WITH_MODIFICATIONS. Your agent learns from the correction and stops repeating the mistake.Security & Compliance
Definitive architectural guides for teams deploying autonomous agents to production. For shared responsibility, DPA, and vendor questionnaires, see Trust & Compliance.
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.
Quickstart
npm install @letsping/sdkCreate a project at letsping.co → Settings → Developers. Copy the generated key.
# .env
LETSPING_API_KEY=lp_live_...Wrapping critical tools also activates automatic behavioral profiling across your agent's execution graph.
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);
}API Reference
Wrapping critical tools also activates automatic behavioral profiling across your agent's execution graph.
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.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.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.BaseCheckpointSaver. Automatically encrypts and persists thread state remotely to bypass serverless memory limits. Drop this into your StateGraph.compile() options.RequestOptions fields
| Field | Type | Description |
|---|---|---|
| service | string | Name of the agent or service (e.g. "billing-agent") |
| action | string | Specific action being requested (e.g. "refund_user") |
| payload | object | The data the human will see and optionally edit |
| priority | "low"|"medium"|"high"|"critical" | Urgency. Affects visual treatment in the dashboard. Default: "medium" |
| schema | Zod schema | JSON Schema | Optional. Generates a typed edit form in the dashboard for payload patching |
| timeoutMs | number | Optional. Max wait in ms. Default: 86,400,000 (24 h) |
| role | string | Optional. 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).
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).
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;schema, the dashboard shows the raw JSON payload as read-only. The operator can still approve or reject, but cannot edit values.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.
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.
# .env — same file as your API key
LETSPING_API_KEY=lp_live_...
LETSPING_ENCRYPTION_KEY=<paste key here>LETSPING_ENCRYPTION_KEY from the environment automatically. No constructor changes needed — your existing lp.ask() calls start encrypting immediately.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.
payload column contains { _lp_enc: true, iv: "...", ct: "..." }. There is no server-side key — LetsPing cannot decrypt it, even with full database access./api/resolve. The SDK decrypts patched_payloadon the way out, so your agent code sees plain objects — not ciphertext.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.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.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.
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.
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.
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.
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.
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.
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.
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.
$10. Tier 3 agents (score 800–1000) unlock up to $100,000 bounties. Score is computed from approval rate, dispute history, and verified attestations.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).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.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.
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.
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(),
})
})
};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.
# 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.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.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.
git clone) wires up the MCP server and mobile companion into your agent workspace without manual configuration.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.
// 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=..."
}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.
// 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 });
}