Verity
/Docs

Core Concepts

This page explains the foundational ideas behind Verity: how effects, leases, and fence tokens work together to guarantee exactly-once execution for real-world actions.

Effects

An effect is a single, protected unit of work — a real-world action that Verity tracks from initiation to completion. Examples:

  • Charging a credit card
  • Issuing a refund
  • Provisioning a virtual machine
  • Sending a notification email
  • Updating a CRM record

Each effect is identified by a unique effect key — a string you choose that represents this specific action instance. Good effect keys are business-meaningful:

"refund:order_48392"           // a refund for a specific order
"provision-vm:tenant_abc"      // VM provisioning for a tenant
"welcome-email:user_42"        // welcome email for a specific user
"crm-update:deal_9001"         // CRM update for a deal

Every effect goes through a lifecycle:

IDLE → RUNNING → COMMITTED  (success)
                → FAILED     (error recorded)
                → EXPIRED    (lease timed out — recovery needed)
Effect keys are idempotency keys. Calling protect() with the same effect key always returns the same result after the first successful commit. This is how Verity prevents duplicate actions.

Leases

A lease is a time-limited, exclusive lock on an effect. When your agent calls protect(), Verity first acquires a lease, giving your agent sole ownership of the action for a specified duration (default: 30 seconds).

Key properties of leases:

  • Exclusive — only one agent can hold the lease at a time. Others receive a 409 Conflict.
  • Time-limited — leases expire after their duration. This prevents dead agents from blocking work forever.
  • Auto-renewable — the SDK automatically renews leases in the background while your act() function runs, preventing expiry during long operations.
  • Configurable — duration ranges from 5s to 120s (default 30s). Renewal starts at 65% of the lease duration.

Lease States

When you request a lease, the response tells you the current state:

StatusMeaningSDK Behavior
grantedYou got the lease. Effect is now RUNNING.Proceeds to observe/act
cached_completedEffect already committed by a prior attempt.Returns cached result immediately
cached_failedEffect previously failed. Admin reset required.Throws EffectPreviouslyFailedError

Prior State

When a lease is granted, the priorState field tells you what happened before:

Prior StateMeaningObserve Behavior
noneBrand-new effect. No prior attempt.Skip observe → go to act()
expiredPrior agent held this lease but it expired. Unknown if action completed.Call observe() to check external system
resetAdmin explicitly reset a failed effect.Skip observe → go to act() (prior attempt definitively failed)

Fence Tokens

A fence token is a monotonically increasing integer assigned to each lease acquisition for an effect. It's the core mechanism that prevents "zombie" agents from causing harm.

Here's the scenario fence tokens prevent:

  1. Agent A acquires lease for refund:order_123 → fence token = 1
  2. Agent A's network stalls. Lease expires.
  3. Agent B acquires lease for same effect → fence token = 2
  4. Agent B processes the refund and commits with fence token 2
  5. Agent A's network recovers. It tries to commit with fence token 1.
  6. Verity rejects the commit — fence token 1 is stale. Agent A is a zombie.

Without fence tokens, Agent A's stale commit could overwrite Agent B's valid result, or worse, trigger a duplicate action. Fence tokens are Verity's equivalent of distributed locking with fencing — a well-established pattern in distributed systems.

You don't manage fence tokens. The SDK handles them automatically. They're visible in the audit trail for debugging and forensics.

The Observe / Act Pattern

The observe and act callbacks are the heart of Verity's crash recovery mechanism.

act() — Execute the Action

This is your real-world side effect. It runs when the effect hasn't been performed yet. It must return the result of the action (which Verity commits to the ledger).

act: async () => {
  return await stripe.refunds.create({
    charge: chargeId,
    amount: 4999,
  });
}

observe() — Check Before Re-Acting

This is called only when priorState === 'expired' — meaning a previous agent held this lease, the lease expired, and Verity doesn't know if the action completed.

Your observe function checks the external system:

  • Return the result if the action already happened (Verity commits it)
  • Return null or undefined to fall through to act()
observe: async () => {
  // Check Stripe for an existing refund
  const existing = await stripe.refunds.list({
    charge: chargeId,
    limit: 1,
  });
  return existing.data.length > 0 ? existing.data[0] : null;
}
When is observe NOT called?
  • priorState: 'none' — brand-new effect, nothing to observe
  • priorState: 'reset' — admin reset a failure, prior attempt definitively failed, no need to check

Decision Flow

protect() called


Request Lease

    ├── cached_completed → return cached result
    ├── cached_failed    → throw EffectPreviouslyFailedError

    └── granted

         ├── priorState: 'none'    → act()
         ├── priorState: 'reset'   → act()
         └── priorState: 'expired' → observe()
                                        ├── found result  → commit observed result
                                        └── null          → act()


                                                          commit result

Namespaces

Namespaces are logical groupings for effects. They serve two purposes:

  1. Organization — group related effects (e.g., payments, notifications, provisioning)
  2. Access control — namespaces can be frozen by admins to block all new actions during incidents

Set a default namespace when initializing the client, or override it per-call:

const verity = new VerityClient({
  baseUrl: '...',
  apiKey: '...',
  namespace: 'payments',  // default for all protect() calls
});

// Override for a specific call
await verity.protect('send-receipt:order_123', opts, {
  namespace: 'notifications',
});

Exactly-Once vs Idempotency Keys

You might be thinking: "Can't I just use idempotency keys?" Here's the difference:

CapabilityIdempotency KeysVerity
Duplicate preventionSingle provider (e.g., Stripe)Any external system
Crash recoveryNone — you re-send blindlyObserve pattern checks first
Zombie protectionNoneFence tokens block stale agents
Audit trailManual loggingAutomatic, structured, queryable
Multi-step workflowsDIY per-step logicWorkflow builder with case + run tracking
Concurrent agent safetyNot handledLeases + fence tokens

Idempotency keys are great when the external system supports them natively (like Stripe). Verity is for everything else — and for coordinating across systems.

Environments (Test / Live)

Verity API keys are prefixed with their environment:

  • vt_test_ — isolated test environment, safe for development
  • vt_live_ — production environment

Data between environments is completely separated via PostgreSQL row-level security. A test API key cannot see or modify live effects, and vice versa. This is enforced at the database level, not just the application layer.

What's Next?