Skip to content

Repository files navigation

Financial Operation Core

Durable execution for agent-initiated financial operations when provider outcomes are uncertain.

Unofficial, experimental, unaffiliated with Razorpay.

▶ Watch the 5-minute demo

Same response-loss scenario, two retry strategies:

Naive retry → 2 provider invocations, 2 financial effects
Financial Operation Core → 2 provider invocations, 1 financial effect


A timeout is not a failed refund

agent requests a refund
      ↓
provider performs the refund
      ↓
the response is lost
      ↓
the caller cannot know whether money moved
      ↓
the retry becomes a SECOND financial effect
   — unless the same business operation keeps the same provider retry identity

Measured in Razorpay Test Mode, 2026-08-10:

Experiment Result
B0-D — response lost after the provider executed, retried with the same key returned the original refund object; total refunds 1
B0-E — two different keys, byte-identical body two refunds

The second row is the trap. The provider is right: a new key is a new operation. A caller that regenerates the key on retry gets no protection at all, and the failure is silent — every response is a valid HTTP 200 with a real refund object. The only symptom is that the merchant paid twice.

Four identities meet at an agent tool boundary

Three of them are per-attempt. Confusing any of them with the fourth causes duplicate money movement.

Identity Lifetime Business identity?
MCP request id (JSON-RPC id) one message no
tool-call id one tool call no
attempt_id one execution attempt no
operation_ref the whole operation yes
provider_idempotency_key the operation provider retry identity, runtime-owned

Reaching for the MCP request id is the natural mistake, and it is invisible: every layer behaves correctly, nothing errors, and two refunds happen. evidence/mutations/mcp_request_id.md records that mutation failing 10 tests, including assert 2 == 1 on the refund count.

Architecture

Agent / Application
      │   supplies trusted operation_ref  (never invented by the model)
      ▼
Financial Operation Core
      │   persists provider idempotency identity, exactly once, never regenerated
      ▼
Razorpay refund adapter
      │
      ▼
Razorpay  (Test Mode, or the measured fake in experiments)

UNKNOWN is a first-class outcome, not an error:

safe_refund({ "operation_ref": "order-991:cancellation-refund:v1",
              "payment_id": "pay_xxx", "amount": 50000 })

// -> { "state": "UNKNOWN",
//      "caller_action": "RETRY_SAME_OPERATION_REF",
//      "do_not": "CREATE_NEW_OPERATION_REF_FOR_THIS_INTENT" }

isError describes the tool call. state describes the money. An uncertain outcome is a successful tool call carrying state: "UNKNOWN" — never "refund failed".

Evidence

Every claim maps to an artifact in EVIDENCE.md. Evidence classes are never blended: real provider and fake provider, real model and deterministic test, are always distinguished.

Claim Class Artifact
Same key + same body replays the original refund MEASURED_REAL_PROVIDER evidence/razorpay-test-mode/raw/004,005
Response lost after execution, same-key retry recovered the original MEASURED_REAL_PROVIDER evidence/razorpay-test-mode/raw/001,002,B0D_proxy_*
Different keys create separate refunds MEASURED_REAL_PROVIDER evidence/razorpay-test-mode/raw/040,041
receipt suppresses a duplicate but returns no refund id MEASURED_REAL_PROVIDER evidence/razorpay-test-mode/raw/052,053
Regenerating the provider key causes a second refund MUTATION_TEST evidence/mutations/provider_key_regeneration.md
Using the MCP request id as business identity causes a second refund MUTATION_TEST evidence/mutations/mcp_request_id.md
Crash recovery, stale-worker refusal, concurrency DETERMINISTIC_TEST tests/
Grant replay is refused by two independent layers MUTATION_TEST evidence/mutations/auth_replay.md, m6a_probe.json
Pilot V1 was invalid and says so MEASURED_REAL_MODEL + MEASURED_FAKE_PROVIDER evidence/phase4/pilot-v1-INVALID/
Model retried an uncertain outcome; runtime reconciled to one refund MEASURED_REAL_MODEL + MEASURED_FAKE_PROVIDER evidence/phase4/pilot-v2/RESULTS_CORRECTED.md
60-trial study is frozen and unrun FROZEN_UNRUN evidence/phase4/frozen-study-UNRUN/
Upstream contribution UPSTREAM_CODE_EVIDENCE razorpay/razorpay-mcp-server#114

On the real-model result

With system-prompt retry guidance and the production tool contract's UNKNOWN → RETRY_SAME_OPERATION_REF signal in context, and with no scenario-level retry instruction, the pinned model retried the same business operation in 3/3 B1 trials, and the runtime reconciled to one refund.

That is a result about the designed system, not about model initiative. The model did not independently discover a retry strategy — it was told, by the product's own contract. See RESULTS_CORRECTED.md, which corrects an earlier overclaim rather than hiding it.

Why not just Temporal?

General workflow engines — Temporal, Restate, DBOS — solve durable execution, and solve it more completely than this does. This project does not claim to replace them, and does not claim durable execution is novel.

The specialization is narrow, and only these five things:

  1. trusted business-operation identity at the financial-tool boundary
  2. provider retry identity kept out of reach of the untrusted agent
  3. cryptographic binding of financial intent to a signed authorization
  4. explicit UNKNOWN semantics inside the tool contract itself
  5. measured Razorpay Refund behaviour rather than assumed behaviour

If you already run a workflow engine and your activities own durable financial identity, use that.

Running it

docker run -d --name fincore-pg -e POSTGRES_PASSWORD=fincore \
  -e POSTGRES_DB=fincore -p 55432:5432 postgres:16-alpine

pip install -e ".[dev]"
export FINCORE_DATABASE_URL=postgresql+psycopg://postgres:fincore@127.0.0.1:55432/fincore

pytest tests -q
python -m fincore.mcp                 # MCP server on stdio

No test requires a Razorpay account, an OpenAI key, or any network access.

What this does not do

LIMITATIONS.md is not a formality — read it. Short version: the guarantee starts after trusted code establishes business identity; resource ownership is trusted rather than proven; Razorpay Test Mode only; UNKNOWN is not background-swept; and provider key retention is unverified by this project.

Upstream contribution

razorpay/razorpay-mcp-server#114 — exposes the Refund API's existing X-Refund-Idempotency header through the MCP create_refund tool. Open for review. Diff and provenance in upstream/.

Provenance

This is a curated public release built from a larger local working tree. See docs/PUBLICATION_PROVENANCE.md.

License

MIT — see LICENSE. Razorpay upstream code is not vendored here; see upstream/UPSTREAM.md.

About

Durable execution for agent-initiated financial operations across retries, crashes, and uncertain provider outcomes.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages