Skip to content

Latest commit

 

History

117 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SoftStop

SoftStop

The Circuit Breaker for Autonomous Agents and Customer Outreach.

Prevent rogue agents, growth loops, and background jobs from spamming your users—across every surface.

Doesn't make software smarter. Makes it stop when it should.

Without SoftStop: messages land and pressure climbs Happy to Churned. With SoftStop: the same chaos hits a shared journal; sparse allows keep pressure capped.

Growth, CRM, Support, Ads, Product, and Agents don’t share a stop signal — so a few customers accumulate pressure until they churn. SoftStop is the shared permit every system checks before anything lands.
See the interactive Why SoftStop canvases →

Docs · Quickstart · Live demo · See pressure live · API · Canonical runtime (`governor/api`) · Examples · Security

CI MIT Node 18+ Docker 0.2.1

Early open source — looking for design partners. Not a claim of wide production adoption.

Why SoftStop?

  • Agent circuit breaking — a safety layer in tool-calling loops, not just frequency capping. check() before the side effect; record() after.
  • Deterministic state for non-deterministic LLMs — offload time/count cooldowns from the prompt. Policy and pressure live on the server.
  • Multi-agent collision prevention — shared per-user permit across Onboarding / Sales / Support (and email / SMS / UI) on separate runtimes.
  • Graceful fallbacks — when blocked, suggestedActionType steers the next move (e.g. interruption → reminder) instead of crash or retry loops.

Authorize only — SoftStop is not a CDP, not a messenger, not tool IAM. See Governing AI Agents.

What SoftStop is

SoftStop answers one question before an escalation runs:

Is it allowed to raise pressure on this user, with this action type, right now?

It does not rate-limit humans. It rate-limits the actors (agents, automations, messaging systems) that want to reach them.

It does not send email, write copy, pick offers, or replace Braze / Resend / your agents. Those systems still decide what to say. SoftStop decides whether they’re allowed to push.

SoftStop does SoftStop does not
check → allow or block Send messages
record the outcome Personalize content
Track user pressure (cost + decay + threshold) Optimize conversion
Enforce cooldowns & caps across systems MCP tool IAM / HITL approvals

Get started

Install

npm i softstop
pip install softstop

Self-host the SoftStop API for production (governor/api is the canonical runtime). softstop.vercel.app is the live demo and SDK CDN — not a production host. Platform / lifecycle eng typically runs the API; Growth, CRM, product, and agents call check / record at send time.

Experimental packages under packages/core|server|storage|gateway are non-canonical — see packages/NON_CANONICAL.md.

Quick start — AI tool call

Prefer the shipped adapters (beforeContact, wrapUserFacingTool, withSoftStop). Outcomes are executed | blocked (not “landed”). On deny, formatBlockedForLlm(decision) (or withSoftStop) returns a stable JSON string for the model — including suggestedFallback / retryAfterMs when present.

import { SoftStop, wrapUserFacingTool, withSoftStop } from 'softstop'

const ss = new SoftStop({ url: process.env.SOFTSTOP_API_URL || 'http://localhost:3000' })

const sendFollowUp = wrapUserFacingTool(
  ss,
  {
    userId: (args) => args.userId,
    actionType: 'urgency',
    surface: 'email',
    actor: 'sales-agent'
  },
  async ({ userId, subject }) => {
    // Resend / SMTP / …
    return { messageId: 'msg_1', userId, subject }
  }
)

const result = await sendFollowUp({ userId: 'user_123', subject: 'Quick follow-up' })

if (!result.ok) {
  // SoftStop already recorded outcome: 'blocked' (+ blockReason)
  // Steer the model — do not crash or retry the same actionType
  return {
    blocked: true,
    reason: result.reason,
    suggestedActionType: result.suggestedActionType // e.g. 'reminder'
  }
}
// SoftStop already recorded outcome: 'executed'

For Vercel AI SDK tool({ execute }), prefer withSoftStop(fn, { client: ss, … }) — blocked paths return formatBlockedForLlm automatically.

Inline without wrapping:

const gated = await ss.beforeContact(
  { userId: 'user_123', actionType: 'urgency', surface: 'email', actor: 'sales-agent' },
  () => sendEmail(/* … */)
)
if (!gated.allowed) {
  // gated.suggestedActionType — record already done with outcome: 'blocked'
}

Raw check / record (same contract):

const decision = await ss.check({
  userId: 'user_123',
  actionType: 'urgency',
  surface: 'email'
})

if (!decision.allowed) {
  await ss.record({
    decisionId: decision.decisionId,
    userId: 'user_123',
    actionType: 'urgency',
    outcome: 'blocked',
    blockReason: decision.reason
  })
  return
}

// escalate, then:
await ss.record({
  decisionId: decision.decisionId,
  userId: 'user_123',
  actionType: 'urgency',
  outcome: 'executed'
})
const status = await ss.getPressure('user_123')
// { pressure, threshold, decayPerHour, costs, updatedAt }

Alternates: github:chonibe/SoftStop#path:packages/sdk-js or https://softstop.vercel.app/softstop.tgz. Browser CDN: https://softstop.vercel.app/sdk.js.

Self-host the API

One-liner (in-memory storage on port 3000):

docker compose up --build
curl -X POST http://localhost:3000/v1/verify

Without Docker:

pnpm install
pnpm dev
pnpm softstop verify

Env: see .env.example (SOFTSTOP_* / GOVERNOR_* aliases). Default image/compose uses GOVERNOR_STORAGE=memory — no database required. Add SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY for persistence (self-host docs).

Deploy on Railway   Deploy to Fly.io

CLI alternatives: fly launch --copy-config (uses fly.toml) · Railway detects Dockerfile / railway.toml.

Latency (honest): local memory POST /v1/check P95 ≈ 0.9 ms on a 2026-08-07 loopback microbench (pnpm bench:check; n=500). Details: docs/perf/PERFORMANCE.md. No hosted/Supabase sub-10ms claim without data.

CI

GitHub Actions runs pnpm test:governor on push/PR (.github/workflows/ci.yml). Badge above links to the latest run.

The contract

Authorize only — SoftStop does not send email, pick offers, or write copy. Prefer the SDK above; raw fetch to /v1/check + /v1/record is equivalent. Read live pressure with GET /v1/users/:userId/pressure.

Hosted APIs use /api instead of /v1. GOVERNOR_API_URL is accepted as a legacy alias for SOFTSTOP_API_URL.

actionType Meaning Default cost Typical surface
urgency Time pressure 40 “ends tonight” email / push
discount Price / promo 30 SMS offer, coupon modal
interruption Modal / popup 25 Checkout upsell, in-app dialog
reminder Soft nudge 15 Agent follow-up, badge

Custom types: add the same key to costs, cooldownHours, and typeCap in a policy file — see action types.

Default threshold: 100. Default decay: 8 points/hour.

When to use

  • Agents — circuit breaker in tool loops; shared permit across Onboarding/Sales/Support (Governing AI Agents)
  • Marketing + CRM — lifecycle, promo, and win-back tools don’t share caps with agents
  • Product UI — modals/banners fire while email/SMS/agents are also pushing
  • Not SoftStop — you need a messaging platform, CDP (identity/journey store), or MCP tool firewall. SoftStop only gates pressure; it does not replace a CDP.

Adoption

SoftStop only protects users when every escalation touchpoint calls check and a matching record. Misuse creates false confidence.

  • POST /v1/verify — integration smoke test
  • GET /v1/health — orphan rate, block rate, health score

orphanRate measures check/record pairing on observed SoftStop traffic. Systems that never call SoftStop never appear in health — low orphan rate is not proof that every actor in the company is wired. Details: Adoption contract

Legacy names

Prefer SoftStop names (SOFTSTOP_API_URL, SOFTSTOP_POLICY, product docs under SoftStop). The HTTP engine still lives under governor/; GOVERNOR_API_URL, GOVERNOR_POLICY, and related aliases remain for backward compatibility. Do not remove them yet.

Repo note: root tenet-policy.json configures contributor boundary lint (scripts/tenet-check.js). It is not a SoftStop pressure policy — runtime packs live in policies/.

Examples

Docs

Start at the docs hub: concept, self-host, policy pack, integration workflow, API.


Contributing · Security · Changelog · Adopters (invite) · License

Press drafts (not customer proof): Press

About

SoftStop — shared permit before any system raises pressure on a user. Authorize-only check/record.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages