Skip to content

feat(qupick): auto-poll crypto payments; pin WEB_CONCURRENCY=1; hosted-first README - #6

Merged
kleczkowski merged 7 commits into
mainfrom
skill/qupick-auto-poll-crypto
Jun 30, 2026
Merged

feat(qupick): auto-poll crypto payments; pin WEB_CONCURRENCY=1; hosted-first README#6
kleczkowski merged 7 commits into
mainfrom
skill/qupick-auto-poll-crypto

Conversation

@kleczkowski

@kleczkowski kleczkowski commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Three changes to the qupick skill, deployment docs, and README.

feat(qupick): auto-poll crypto payments until settled

Adds step 6a to skills/qupick/SKILL.md. Conventional crypto payments
(onchain_match, and any return_payment_link invoice that comes back
unpaid) are now polled automatically every ~30s until a terminal state,
capped at the invoice's expiration_minutes (fallback 60 min), instead of
requiring a manual "check to poll" round-trip. Balance settlements
(account_match / account_fiat) clear instantly and skip the step.

Retune gating is unchanged and explicit per terminal state:

  • payment_confirmed → worst performer was sold → retune, keep polling for delivery
  • complete / all_delivered → fetch redemption code / PIN / QR, finish
  • expired or cap elapsed while unpaid → stop, no retune, offer re-issue
  • partial_payment → stop, no retune, surface the shortfall

docs: pin WEB_CONCURRENCY=1 to keep MCP sessions intact

Corrects two rows in docs/deployment-prep.md that claimed the server is
stateless and WEB_CONCURRENCY is safe to scale to vCPU count. The mounted
MCP server (FastApiMCP.mount_http) keeps its Streamable-HTTP session table
in per-worker memory (fastapi-mcp 0.4.0 hardcodes stateless=False), so with
more than one uvicorn worker and no shared session store or sticky routing, a
session minted on one worker 404s when a follow-up call (tools/list, tool
invocations) lands on another. On the live box this produced ~17% 404s on
POST /mcp; pinning to one worker eliminated them.

docs: lead README with hosted register/setup, move local to Development

Restructures README.md to lead with the hosted server. A new
Register & set up (hosted) section gives a 5-step path against
https://qupick.quip.network (install → configure defaults → point
.mcp.json at the hosted URL → register_agent, key emailed → set
QUPICK_API_KEY and reconnect). The local backend instructions (docker
compose / uv, console-key registration, cold-start note) move to a new
Development section, and the Configure anchor that pointed at the removed
Connect section is fixed.

Add step 6a: conventional crypto payments (onchain_match and any
return_payment_link invoice that comes back unpaid) are polled
automatically every ~30s until a terminal state, capped at the
invoice's expiration_minutes, instead of requiring a manual check.

Retune stays gated: retune on payment_confirmed; no retune on
expiry or partial_payment.
The MCP mount keeps its Streamable-HTTP session table in per-worker
memory (fastapi-mcp hardcodes stateless=False), so >1 uvicorn worker
404s MCP follow-up calls (tools/list, tool invocations) ~half the
time. Correct the prior claim that the server is stateless and that
WEB_CONCURRENCY is safe to scale to vCPU count.
@augmentcode

augmentcode Bot commented Jun 29, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR updates the Qupick operator docs/skill spec to reduce manual intervention during Bitrefill crypto checkouts and to harden production deployment guidance.

Changes:

  • Adds Qupick skill step 6a to auto-poll on-chain Bitrefill invoices roughly every ~30s until a terminal status or a time/expiry cap, removing the need for user “check to poll” prompts.
  • Clarifies retune gating: retune can occur once payment is confirmed (worst performer spent), and polling continues until delivery completes.
  • Updates docs to reflect that Claude Code does not expand environment variables in .mcp.json headers, so the Qupick API key must be pasted literally into the Bearer header (file is gitignored).
  • Adjusts deployment guidance to pin WEB_CONCURRENCY=1 because fastapi-mcp keeps Streamable-HTTP session state per-worker, causing intermittent 404s when multiple uvicorn workers are used.
  • Restructures the README to be hosted-first (register/setup against https://qupick.quip.network) and moves local backend instructions into a dedicated Development section.

Technical Notes: The guidance relies on fastapi-mcp dispatching tool calls through the FastAPI app (with forwarded Authorization) so public bootstrap tools remain usable while per-agent tools stay Bearer-gated.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread skills/qupick/SKILL.md Outdated
3. **Terminal states:**
- `payment_confirmed` → the crypto was actually spent; the worst performer is sold. The retune
gate (step 7) is now satisfied — retune immediately, then keep polling for delivery.
- `complete` / `all_delivered` → fetch the redemption code / PIN / QR and finish.

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

complete / all_delivered is listed as a terminal state, but the Bitrefill MCP reference docs in this repo only document invoice statuses unpaid → payment_detected → payment_confirmed → complete; if all_delivered isn’t a real status, this could cause the polling loop to never terminate on delivery.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0d23e39. Removed all_delivered — the repo's Bitrefill reference (mcp.md, api.md) documents the status enum as unpaid → payment_detected → payment_confirmed → complete, so complete is the sole delivery terminal. The loop also has a hard ~60 min time cap as a status-independent backstop.

Comment thread skills/qupick/SKILL.md Outdated
1. Show the pay address / link / amount once, so the user can broadcast the transaction.
2. Then poll `get-invoice-by-id` on a fixed cadence — roughly **every 30 s**, automatically, without
waiting for the user — until the invoice reaches a terminal state. **Cap the loop at the
invoice's own `expiration_minutes`** (≈60 min; fall back to 60 min if the field is absent). Space

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step depends on an invoice field named expiration_minutes, but I don’t see that field documented elsewhere in the repo’s Bitrefill MCP/REST references; if it’s absent, implementations may incorrectly cap (or fail to cap) polling.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0d23e39. The spec no longer names expiration_minutes (not present in any Bitrefill reference here). It now caps at ~60 min, or the invoice's reported expiry if it exposes one, so termination no longer depends on a specific field name.

Comment thread skills/qupick/SKILL.md Outdated
2. Then poll `get-invoice-by-id` on a fixed cadence — roughly **every 30 s**, automatically, without
waiting for the user — until the invoice reaches a terminal state. **Cap the loop at the
invoice's own `expiration_minutes`** (≈60 min; fall back to 60 min if the field is absent). Space
the polls with a wait that survives across turns (schedule the next re-check) rather than blocking

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Schedule the next re-check … rather than blocking the session” reads like it requires a background/heartbeat scheduler; if that capability isn’t available in some runtimes, this spec may be hard to implement as written without falling back to blocking waits or user-driven polling.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0d23e39. Reworded to: use a cross-turn wake-up if the runtime offers one, otherwise fall back to a blocking wait or polling on the user's next message — no scheduler is required.

Comment thread docs/deployment-prep.md
| **Solvers** | **SA-only for v1 (PoC/MVP)** | `GUROBI_IN_RACE=0` (no Gurobi license in prod) and `DWAVE_API_TOKEN` unset (no QPU cost). Simulated annealing is the entire race field and always runs. Reversible later via env alone — re-add Gurobi or the QPU without a code change or image rebuild. |
| **Market data** | **Live assets-api** | `MARKET_DATA_SOURCE=assets-api` pointed at `https://asset-tracker.quip.network`, so the μ/Σ inputs reflect real market conditions. Adds a runtime dependency on that service being reachable; `synthetic` stays the offline fallback if it is down or unbuilt. |
| **Concurrency** | **Scale workers to vCPU** | `WEB_CONCURRENCY` = the droplet's vCPU count. The server is stateless in-process (no background loops), so this is safe; keep `workers × ~15` DB connections under the Supabase pooler cap. |
| **Concurrency** | **Pin `WEB_CONCURRENCY=1`** | The mounted MCP server (`FastApiMCP.mount_http`) keeps its Streamable-HTTP session table **in each worker's memory** (fastapi-mcp 0.4.0 hardcodes `stateless=False`). With >1 uvicorn worker and no shared session store or sticky routing, a session minted on worker A 404s when a follow-up (`tools/list`, tool calls) round-robins to worker B — intermittent MCP failures. The CPU-bound solve already offloads via `asyncio.to_thread`, so one worker handles concurrent requests fine. To scale workers you must first make MCP stateless or add a shared session backend. |

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This update correctly calls out per-worker MCP session state, but it now conflicts with the earlier statement in this doc that the server “holds no in-process state”; consider reconciling that wording so operators don’t conclude multi-worker is safe elsewhere in the checklist.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0d23e39. Scoped the line-19 claim to in-process application state (the removed mark-to-market loop) and added a parenthetical pointing at the per-worker MCP session state and the WEB_CONCURRENCY=1 pin, so the two rows no longer conflict.

Add a 5-step hosted register & set up section (qupick.quip.network) as
the primary path, and relocate the local backend instructions (docker
compose / uv, console-key registration) to a Development section. Fix
the Configure anchor that pointed at the removed Connect section.
@kleczkowski kleczkowski changed the title feat(qupick): auto-poll crypto payments; pin WEB_CONCURRENCY=1 feat(qupick): auto-poll crypto payments; pin WEB_CONCURRENCY=1; hosted-first README Jun 29, 2026
Claude Code does not expand ${VAR} in .mcp.json headers, so the
Authorization: Bearer ${QUPICK_API_KEY} pattern left the per-agent MCP
tools unauthenticated (401). Paste the key literally into the header
instead; .mcp.json is gitignored, so the secret stays local. The
QUPICK_API_KEY env var now only documents its real consumer,
backend/scripts/mcp_smoke.py.
Address review feedback on PR #6:
- drop the undocumented `all_delivered` terminal status; the reference
  enum is unpaid -> payment_detected -> payment_confirmed -> complete
- stop keying the poll cap on the unverified `expiration_minutes` field;
  cap at ~60 min (or the invoice's reported expiry) so the loop always
  terminates
- give the cross-turn scheduler a fallback (blocking wait / next-message
  poll) for runtimes without one
- reconcile deployment-prep: scope the 'no in-process state' claim to
  application state and point at the per-worker MCP session caveat
@kleczkowski

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread skills/qupick/SKILL.md Outdated
`unpaid → payment_detected → payment_confirmed → complete`):
- `payment_confirmed` → the crypto was actually spent; the worst performer is sold. The retune
gate (step 7) is now satisfied — retune immediately, then keep polling for delivery.
- `complete` → fetch the redemption code / PIN / QR and finish.

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

complete says to “fetch the redemption code / PIN / QR”, but the Bitrefill MCP reference in this repo indicates redemption info is retrieved via get-order-by-id rather than get-invoice-by-id; without clarifying which tool/field to use here, an implementation may not be able to reliably retrieve the delivered code after settlement.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified in 8ef4362, but using get-invoice-by-id, not get-order-by-id. The Bitrefill MCP server mounted here does not expose get-order-by-id — the live tool set is buy-products / get-invoice-by-id / get-product-details / list-invoices / search-products / submit-prepayment-step / update-order. SKILL.md already records this at the end of step 6 ("there is no get-order-by-id tool"); get-order-by-id only appears in the bitrefill reference docs, which describe the fuller upstream MCP. Step 6a now names get-invoice-by-id explicitly so there's no ambiguity. (The reference/live discrepancy in the bitrefill skill is real but separate from this PR.)

Comment thread skills/qupick/SKILL.md Outdated
- `payment_confirmed` → the crypto was actually spent; the worst performer is sold. The retune
gate (step 7) is now satisfied — retune immediately, then keep polling for delivery.
- `complete` → fetch the redemption code / PIN / QR and finish.
- `expired`, or the cap elapses with the invoice still `unpaid` → **stop** and report that the

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This treats expired as an invoice status, but the Bitrefill MCP reference in this repo only documents unpaid → payment_detected → payment_confirmed → complete; if expired isn’t a real status value, the loop termination should be described purely in terms of the time cap / invoice expiry timestamp to avoid implementations waiting for a state that never arrives.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8ef4362. Termination on a stuck-unpaid invoice now rests on the time cap (or the invoice's expiry), not an expired status — the bitrefill references treat expiry as a 180-min time concept, not a documented status value, so the loop no longer waits on a state that may never appear.

Second review pass on PR #6:
- step 6a 'complete' now names get-invoice-by-id for the redemption
  code (the mounted Bitrefill MCP server does not expose
  get-order-by-id; the step-6 note already records this)
- loop termination on a stuck-unpaid invoice now rests on the time cap
  / expiry rather than an 'expired' status string, which the Bitrefill
  reference does not document
@kleczkowski

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread README.md Outdated
4. **Register to get your API key.** With the placeholder still in place, start Claude Code and ask
the agent to proceed. `register_agent` is a **public** tool — it works without a valid key — and it
is called with your `config.defaults` (name, email, sliders); the server **emails your API key** to
that address (it is never shown in the response). A repeat name or email returns `409 already

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README.md:45 — This says duplicates return 409 already registered, but the backend error detail is "email or name already taken" (see backend/src/backend/api/routes.py); the mismatch could confuse operators who see a different message.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@kleczkowski kleczkowski Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 583ef0b. README now quotes the actual 409 details — email already registered / name already taken / email or name already taken — matching routes.py instead of paraphrasing as 'already registered'.

Third review pass on PR #6: the backend returns 409 with detail
'email already registered' / 'name already taken' (or 'email or name
already taken'), not the paraphrased 'already registered'
(backend/src/backend/api/routes.py).
@kleczkowski
kleczkowski merged commit 73b62e6 into main Jun 30, 2026
1 check passed
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.

1 participant