Skip to content

Add deadline-aware x402 settlement for upto sessions#125

Closed
adambalogh wants to merge 1 commit into
mainfrom
claude/x402-settlement-drops-rhjtuw
Closed

Add deadline-aware x402 settlement for upto sessions#125
adambalogh wants to merge 1 commit into
mainfrom
claude/x402-settlement-drops-rhjtuw

Conversation

@adambalogh

Copy link
Copy Markdown
Contributor

Summary

Implements deadline-aware settlement for x402 upto sessions to fix three silent failure modes in og-x402's stock middleware that cause revenue loss:

  1. Sessions outliving their Permit2 deadline (permit expires before settlement completes)
  2. Async facilitator jobs treated as settled at enqueue time (202 Accepted) rather than confirmed on-chain
  3. Sessions refusing cost after response streams, becoming permanently un-exhaustible

Key Changes

  • New module x402_settlement.py: Provides deadline-aware session management and settlement lifecycle

    • DeadlineAwareSessionStore: Extends og-x402's SessionStore to:
      • Track Permit2 deadlines per session
      • Hide sessions from serving paths before deadline becomes unsettleable (clients get 402 and sign fresh permit)
      • Saturate spend cap instead of refusing cost, so exhausted sessions settle promptly
      • Track in-flight facilitator settlement job IDs
    • install_deadline_aware_settlement(): Monkey-patches PaymentMiddleware to:
      • Settle sessions before their permit deadline (not just when idle/exhausted)
      • Poll facilitator async jobs to terminal state before closing sessions
      • Re-drive settlement when jobs fail (if time permits) or abandon with critical logging if deadline passed
      • Clean up idle sessions with zero cost (no settlement needed)
  • Updated __main__.py:

    • Replaces default SessionStore with DeadlineAwareSessionStore
    • Calls install_deadline_aware_settlement() during middleware initialization
    • Passes X402_SESSION_MAX_TIMEOUT_SECONDS to session creation
  • Updated definitions.py:

    • Adds X402_SESSION_MAX_TIMEOUT_SECONDS constant (permit lifetime in seconds)
    • Documents relationship to SERVE_MARGIN_SECONDS and SETTLE_MARGIN_SECONDS
  • New test module test_x402_settlement.py:

    • 20+ tests covering store behavior (deadline tracking, cost saturation, session hiding)
    • Settlement lifecycle tests (async job success/failure, deadline hopelessness, sync transactions)

Implementation Details

  • Uses configurable margins (SERVE_MARGIN_SECONDS=420s, SETTLE_MARGIN_SECONDS=120s) to provide buffer for reaper cadence, facilitator queue latency, and on-chain confirmation
  • Polls facilitator jobs with per-cycle budget (JOB_POLL_BUDGET_SECONDS=30s) to avoid blocking the reaper; resumes next cycle if budget exhausted
  • Abandons sessions with critical logging when deadline becomes hopeless (DEADLINE_HOPELESS_SECONDS=30s) to surface revenue loss
  • Follows existing monkey-patch pattern in __main__.py to avoid forking og-x402 (pinned dependency)
  • Thread-safe session bookkeeping (deadlines, job IDs) using existing SessionStore._lock

https://claude.ai/code/session_01CbDr9d3YxeSjdwJQUzjuEC

A shared-wallet run of many sequential requests rode a single upto session
whose Permit2 deadline was signed on the first request with the default 300s
lifetime. The session only settled after going idle, by which point the
permit had expired and settlement could never land — and the facilitator's
202 (job enqueued) was treated as settled, so the session was deleted before
the on-chain result was known. Whole runs settled to nothing.

New tee_gateway/x402_settlement.py (monkey-patches og-x402's Flask middleware,
same pattern __main__ already uses, so the pinned dependency and uv.lock are
untouched):

- DeadlineAwareSessionStore tracks each session's permit deadline, hides it
  from the serving paths SERVE_MARGIN_SECONDS before expiry (client signs a
  fresh permit) and saturates the spend cap instead of serving unbillable
  requests once the cap is hit.
- Reaper settles sessions approaching their deadline (not just idle/exhausted),
  polls GET /settle/<jobId> to a terminal state before marking a session
  settled, re-drives settlement on a failed job while the permit still allows
  it, and abandons loudly (CRITICAL) only when the deadline has truly passed.

__main__/definitions: set an explicit X402_SESSION_MAX_TIMEOUT_SECONDS (3600)
on all upto routes — the old implicit 300s was shorter than the serve margin —
and wire in the new store + settlement install.

Adds test_x402_settlement.py (13 tests).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CbDr9d3YxeSjdwJQUzjuEC
@adambalogh
adambalogh marked this pull request as ready for review July 9, 2026 00:00
@adambalogh adambalogh closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants