Actspan Docs Quickstart
DocsExamples

Run the public OpenAI refund walkthrough, inspect local approvals and check payment receipts; compare provider coverage.

On this page

Try a governed refund agent

Run three synthetic refunds with the public Actspan 0.2.0 CLI and native OpenAI: $75 pays automatically, $250 waits for a local review, and $750 is refused. Check the tool receipts against the sandbox ledger after each case. No real money moves. Claude and LangChain coverage is listed below the walkthrough.

Choose your entry

  • Live workflow: continue below with your OpenAI key to observe payment, review and refusal against synthetic ledgers.
  • No model key: the receipt-check exercise uses a scripted loopback provider and checks four receipt cases with no paid model calls. It still needs the public CLI, extracted examples and host Python.

Before you begin

Use the public CLI and examples archive. The current native download supports Apple Silicon macOS. Starting with that archive, all three cases and the review commands are on this page. You also need host Python 3.10+ (python3) for ledger inspection and a working OPENAI_API_KEY exported privately in this terminal. Live runs incur OpenAI charges; setup does not. This path uses the bundled OpenAI runtime and gpt-4.1-mini, without a separate provider-plugin installation or Cloud account.

The native CLI runs its tools with bundled Python, but commands such as python3 refund_service.py ledger and the receipt-check script use a separate Python installed on your computer. The CLI does not install that host Python. On a Mac without one configured, python3 may prompt you to install Apple's Command Line Tools. Check python3 --version and prepare Python 3.10+ before starting this walkthrough.

Approval scope in 0.2.0: the unchanged example can complete local $250 approval. Its explicit file entrypoints avoid the known package-resolution defect. Imported helpers remain outside automatic approval-change detection in that release. Keep the example files, identity and ledger fixed while paused; start a fresh run after changing them. The approval guide explains the affected configurations and the limits of this workaround.

Problem and flow

A customer asks for money back. A convincing reply can still hide an unauthorized payment or a fabricated ID. This example separates the service's refund limits, Actspan's review checkpoint, the completed tool receipt and the persisted ledger.

Refund flow

Question Evidence to inspect
Was payment allowed now? Quote tier and selected workflow branch.
Did a review occur first? The saved refund_review decision before the reviewed payment tool.
Did the action really happen? Matching completed receipt plus the payment row in SQLite.

Set up a fresh sandbox

Enter the OpenAI refund directory from the public archive. Adjust the first path if you extracted it elsewhere. Use one new ledger, leaving earlier ledgers intact:

cd "$HOME/actspan/actspan-examples/openai/refund_agent"
actspan --version
python3 --version

mkdir -p .demo
REFUND_TRIAL_DIR=$(mktemp -d "$PWD/.demo/walkthrough.XXXXXX")
export REFUND_LEDGER_DB="$REFUND_TRIAL_DIR/refunds.sqlite3"
export REFUND_CUSTOMER_ID=CUST-100
export ACTSPAN_PORTAL_DISABLED=1

actspan init workspace.yaml --no-env-file --no-portal
actspan validate workspace.yaml --strict-env
actspan preview workspace.yaml --strict-schema --no-portal

Expected: version 0.2.0, successful validation and a preview with no model call. Export the customer identity before the first run; request text does not authenticate a customer. Keep this terminal and working directory for continuation.

Understand the rules

The sandbox service reads these values from refund_policy.yaml (JSON is valid YAML):

{
  "auto_refund_max_usd": 100,
  "human_approval_max_usd": 500,
  "require_photo_evidence": true
}

For a qualifying damage claim, up to $100 is automatic; above $100 through $500 needs review; above $500 is blocked. Each supplied claim belongs to CUST-100, has photos, and requests less than its order total.

Layer What enforces the decision
Sandbox service Classifies the quote using monetary thresholds; checks customer, claim, quoted amount, policy hash and payment path before writing.
Actspan workflow and policy Limits the assessor to read/quote tools, withholds the reviewed payment step until its approval checkpoint is approved, and restricts each agent's tools.
Actspan output evidence Requires configured quote/payment claims to match completed tool receipts.
Ledger inspection Lets you compare those claims with persisted sandbox rows independently.

The service owns the monetary rule. It does not independently authenticate the reviewer; the local CLI decision controls entry to the reviewed workflow step.

Run the $75 automatic refund

Automation limit: public 0.2.0 can report exit 0/outer success for the separate direct-prompt receipt-failure path even when nested execution failed. Do not generalize a successful shell exit to a verified business write. Inspect nested status, the same-run recorded error and ledger; use the model-free checker exercise for its supported status checks. The source correction requires a newly qualified release.

actspan run workspace.yaml refund_request --json --no-portal --no-cache \
  --input-text 'Claim CLM-1001 on order ORD-1001. Two mugs arrived cracked and I uploaded photos. I want a $75 refund.' \
  > "$REFUND_TRIAL_DIR/75.json"
cat "$REFUND_TRIAL_DIR/75.json"
python3 refund_service.py ledger

Expected: outcome: refunded with a generated PAY-… payment ID and QTE-… quote ID. The ledger has exactly one payment: CLM-1001, amount_cents: 7500, path: auto. Both IDs must match the final output. Your IDs will differ.

Run the $250 human-review case

actspan run workspace.yaml refund_request --json --no-portal --no-cache \
  --input-text 'Claim CLM-1002 on order ORD-1002. My $400 headphones arrived damaged and I uploaded photos. I want a $250 refund.' \
  > "$REFUND_TRIAL_DIR/250.json"
cat "$REFUND_TRIAL_DIR/250.json"

RUN_250=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["result"]["run_id"])' "$REFUND_TRIAL_DIR/250.json")
THREAD_250=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["result"]["output"]["thread_id"])' "$REFUND_TRIAL_DIR/250.json")

actspan inspect "$RUN_250" --node refund_review --json
actspan inspect "$RUN_250" --trace --json
python3 refund_service.py ledger

Expected: pending_approval; inspection shows the saved claim, $250 amount and quote at refund_review. The ledger contains an approval-tier quote for CLM-1002, no payment for that claim, and only the earlier $75 payment. The trace calls this checkpoint paused_approval.

Approve the saved request:

actspan continue --thread-id "$THREAD_250" --approve \
  --actor demo-reviewer --note 'Reviewed the synthetic damage claim.' --json \
  > "$REFUND_TRIAL_DIR/250-approved.json"
cat "$REFUND_TRIAL_DIR/250-approved.json"
python3 refund_service.py ledger

continue --approve supplies the decision and resumes execution; no second resume command is needed. Expected: one $250 payment for CLM-1002 with amount_cents: 25000, path: reviewed and matching quote/payment IDs. There are now two payments in total. --actor is a caller-supplied audit label, not an authenticated reviewer identity. Portal reporting stays disabled in this walkthrough.

Run the $750 blocked refund

actspan run workspace.yaml refund_request --json --no-portal --no-cache \
  --input-text 'Claim CLM-1003 on order ORD-1003. The desk arrived damaged and I uploaded photos. I want a $750 refund.' \
  > "$REFUND_TRIAL_DIR/750.json"
cat "$REFUND_TRIAL_DIR/750.json"
python3 refund_service.py ledger

Expected: a quote with tier: blocked, final outcome: blocked, an empty payment ID and no payment for CLM-1003. The main walkthrough's total remains two payments: the automatic $75 and the approved $250.

In the qualified run the service classified the quote as blocked, and the settlement agent returned that outcome without calling a payment tool. This is not an observed Actspan TOOL_DENIED dispatch. The service also independently rejects a payment attempt against a blocked quote. The workflow itself can finish with execution status succeeded and exit code 0 while its business outcome is blocked.

Match the final output to the ledger

Normal successful runs store the final object as a JSON string at result.output; successful approval continuation uses result.final_output. The pending run instead has an object at result.output, including thread_id. Print the final outcome after approval:

python3 - "$REFUND_TRIAL_DIR/250-approved.json" <<'PY'
import json
import sys
result = json.load(open(sys.argv[1]))["result"]
value = result.get("final_output", result.get("output"))
print(json.dumps(json.loads(value) if isinstance(value, str) else value, indent=2))
PY
python3 refund_service.py ledger

Use 75.json or 750.json instead to inspect another completed case. For refunded or already_refunded, compare amount_usd * 100 with the payment row's amount_cents and compare both IDs literally. For declined or blocked, verify there is no payment for the claim; a declined output reports amount zero while its original $250 quote remains. A model saying “refunded” does not establish payment.

Optional: try rejection in a separate sandbox

This exercise starts with no payments and does not rerun $75. Use a subshell so its ledger, files and thread variables do not replace the main walkthrough's:

(
set -eu
REJECTION_TRIAL_DIR=$(mktemp -d "$PWD/.demo/rejection.XXXXXX")
export REFUND_LEDGER_DB="$REJECTION_TRIAL_DIR/refunds.sqlite3"
export REFUND_CUSTOMER_ID=CUST-100
export ACTSPAN_PORTAL_DISABLED=1

actspan run workspace.yaml refund_request --json --no-portal --no-cache \
  --input-text 'Claim CLM-1002 on order ORD-1002. My $400 headphones arrived damaged and I uploaded photos. I want a $250 refund.' \
  > "$REJECTION_TRIAL_DIR/250.json"
REJECTION_THREAD=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["result"]["output"]["thread_id"])' "$REJECTION_TRIAL_DIR/250.json")
actspan continue --thread-id "$REJECTION_THREAD" --reject \
  --actor demo-reviewer --note 'Declined the synthetic damage claim.' --json \
  > "$REJECTION_TRIAL_DIR/250-rejected.json"
cat "$REJECTION_TRIAL_DIR/250-rejected.json"
python3 refund_service.py ledger
)

Expected: outcome: declined, amount zero, an empty payment ID, the original $250 quote and zero payments in this separate ledger. Each pending request receives one decision. The main walkthrough's 250-approved.json and ledger remain available. If you also run $750 in this rejection sandbox, its payment count still stays zero; a blocked quote creates no payment.

Optional: test receipt validation failures

The example's output_evidence rules require a quoted claim to match a quote_refund completion. For refunded or already_refunded, this payment rule requires all mapped fields from one successful tool receipt:

output_evidence:
- name: payment_receipt
  tools: [issue_auto_refund, issue_reviewed_refund]
  when:
    /outcome: [refunded, already_refunded]
  result_when:
    /status: [sandbox_succeeded, already_refunded]
  match:
    /payment_id: /payment_id
    /quote_id: /quote_id
    /amount_usd: /amount_usd

These are explicit checks on configured output fields, not verification of every natural-language sentence.

Download receipt-check revision 2 and its checksum file. Save both files using their original names in your Downloads directory. This separate docs download is not inside the immutable examples archive. From the OpenAI example directory, in the same terminal used above:

(
  set -eu
  REFUND_EXAMPLE_DIR="$PWD"
  cd "$HOME/Downloads"
  shasum -a 256 -c refund-receipt-check-2_SHA256SUMS.txt
  unzip refund-receipt-check-2.zip -d "$REFUND_TRIAL_DIR"
  python3 "$REFUND_TRIAL_DIR/refund-receipt-check/run.py" \
    --actspan "$(command -v actspan)" \
    --example-dir "$REFUND_EXAMPLE_DIR" \
    --output "$REFUND_TRIAL_DIR/receipt-check"
  cat "$REFUND_TRIAL_DIR/receipt-check/qualification.json"
)

The fixture uses Python's standard library, a loopback scripted Responses endpoint, your actual 0.2.0 executable and copies of the public refund service and tools. It gives the child process a dummy key and separate state/ledgers; it does not call a paid model, modify the runtime or reuse your walkthrough's ledger. Its synthetic declaration exercises the payment-evidence mapping above, not the full live agent workflow or model behavior.

Expected results:

Controlled response Runtime result Payments left in that case's sandbox ledger
Invented payment ID, no tool call failed, OUTPUT_EVIDENCE_UNSUPPORTED_CLAIM recorded 0
Genuine successful tool receipt succeeded, IDs and amount match 1
Incorrect reported payment ID after a real sandbox write failed, unsupported claim recorded 1
Payment ID from one receipt mixed with quote/amount from another failed, unsupported claim recorded 2

Each case retains run.stdout, completed tool receipts, local execution records and ledger-after.json for inspection. On this published direct-prompt path, the shell can exit 0 and the outer JSON can say success: true even when result.status is failed. The fixture checks the nested execution status and the recorded error for that run; shell success alone is insufficient.

A failed output-evidence check rejects the reported result; it does not reverse a completed sandbox payment. Inspect the ledger before retrying. Tool callbacks show observed execution, while persisted rows show what the sandbox recorded. Neither establishes general exactly-once payments.

Provider and review coverage

Path Qualified behavior Boundary
Public native CLI 0.2.0, bundled OpenAI $75 automatic, $250 local CLI approval and $750 blocked; matching ledger IDs The complete public walkthrough above. Local actor labels do not authenticate a reviewer.
Public native CLI 0.2.0, bundled Claude One $75 automatic refund with a matching receipt Public bundle $250/$750 review walkthrough has not been qualified.
Installed core 0.2.0, isolated Anthropic 0.1.0 $75, $250 approve/reject, $750 and foreign-identity checks Separate Python/plugin installation; does not extend the public-bundle claim.
LangChain 0.1.1 with core 0.2.0, OpenAI or Claude $75 pays with receipts; $250 blocks before payment; $750 pays nothing Local $250 denial is not human review. The separate Cloud approval workspace remains unqualified on these artifacts.
Native OpenAI and Claude, authenticated local portal Pause, human review, changed-configuration refusal and reviewed payment Historical core 0.1.2 check; the complete portal path was not rerun on 0.2.0. Production OIDC is separate.

For a Claude alternative, keep the archive's sibling directories together and use actspan-examples/anthropic/refund_agent with ANTHROPIC_API_KEY. Its model is claude-haiku-4-5-20251001; the shared ledger command is python3 ../../openai/refund_agent/refund_service.py ledger. Start with a new ledger and the $75 request; do not infer the untested public cases from the isolated-plugin results. See Anthropic setup.

Independent OpenAI 0.1.1/Anthropic 0.1.0 plugins and the partner-only LangChain examples need host Python and supplied package access. See provider requirements and LangChain tool approvals. Configuration-drift follow-on tests and portal history are documented in the approval guide; they are not additional completed steps of this public walkthrough.