@keelstack/guard gives you production safety rails fast: deterministic idempotency, spend limits, risk gates, and failure policies. Ship now, then add @keelstack/guard-redis + OTel spans in beta before public launch.
LangGraph, Vercel AI SDK, Mastra, OpenAI Agents SDK — they all retry failed or timed-out tool calls. Without application-level deduplication, your action fires multiple times.
sendEmail(). Network blips at 2s. Framework retries after timeout. Email sends twice.stripe.charges.create(). Stripe responds slow. Agent retries with same params.Works with any async () => T. No framework coupling. No config to start.
import { guard } from '@keelstack/guard'; // Agent calls sendWelcomeEmail(). Network blips. Agent retries. // Without guard → email sent twice. // With guard → second call returns cached result. Email sent once. const result = await guard({ key: `send-welcome:${userId}`, // stable, unique per operation action: () => resend.emails.send({ to: user.email, subject: 'Welcome to the app!', }), });
const result = await guard({ key: `ai-call:${userId}:${requestId}`, action: () => openai.chat.completions.create({ model: 'gpt-4o', messages: [...], }), budget: { id: userId, // per-user budget limitUsd: 2.00, // hard cap: $2 per day warnAt: [0.5, 0.8], // warn at 50% and 80% onWarn: ({ percentUsed, id }) => console.warn(`User ${id}: ${(percentUsed * 100).toFixed(0)}% of AI budget used`), }, extractCost: (res) => (res.usage.total_tokens / 1_000_000) * 15, }); if (result.status === 'blocked:budget') { return Response.json({ error: 'Daily AI budget exceeded' }, { status: 429 }); }
const result = await guard({ key: `delete-account:${userId}`, action: () => db.users.delete({ where: { id: userId } }), risk: { level: 'irreversible', // 'safe' | 'reversible' | 'irreversible' policy: 'block', // 'allow' | 'log' | 'warn' | 'block' onRisk: (info) => { auditLog.write({ key: info.key, level: info.level, blocked: info.blocked, }); }, }, }); if (result.status === 'blocked:risk') { return Response.json({ error: 'Action blocked by risk policy' }, { status: 403 }); }
// All three primitives — idempotency + budget + risk — on one call. const result = await guard({ key: `agent-action:${userId}:${taskId}`, action: () => stripe.charges.create({ amount: amountCents, currency: 'usd', }), budget: { id: userId, limitUsd: 50 }, extractCost: (res) => res.amount / 100, risk: { level: 'irreversible', policy: 'log', onRisk: auditLog.write }, }); // Guards run in order: idempotency → budget → risk → execute // result.status: 'executed' | 'replayed' | 'blocked:budget' | 'blocked:risk'
Good idempotency depends on deterministic keys. The guide below mirrors the README advice so teams avoid accidental duplicate side effects in production.
await guard({ key: `welcome:${Date.now()}:${Math.random()}`, action: () => sendWelcomeEmail(user), }); // Every retry generates a different key. // Deduplication cannot replay safely.
await guard({ key: `welcome-email:${user.id}`, action: () => sendWelcomeEmail(user), }); // Same operation + same entity => same key. // Retries replay the stored result instead of firing twice.
Idempotency, budget, risk, and FailureConfig policies in a tiny surface area. Production behavior without framework lock-in.
No coupling. If it returns Promise<T>, you can guard it.
Join the beta to run guard with a first-party Redis adapter and native OpenTelemetry spans. Keep your idempotency ledger in Redis, trace every replay/block in your existing observability stack, and shape the dashboard with direct feedback.
Checklist: ✅ Idempotency ✅ Budget ✅ Risk gate ✅ FailureConfig · Coming: First-party Redis adapter, Dashboard, OTel spans
Free core SDK stays MIT forever. Dashboard is the paid layer.