一键导入
build-with-agent-team
Spawn an agent team to build the trusted payment infrastructure backend in parallel
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Spawn an agent team to build the trusted payment infrastructure backend in parallel
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | build-with-agent-team |
| description | Spawn an agent team to build the trusted payment infrastructure backend in parallel |
| user-invocable | true |
Build the "Agent-Safe Shopping + Payments" backend. $ARGUMENTS
This is a TypeScript/Node.js monolith (Fastify + Prisma + BullMQ + Stripe Issuing). Spawn 7 agents as described below. Follow the strict spawn order and contract handoff protocol — agents must not guess types or interfaces; they must import from the shared contracts Agent 1 publishes.
src/contracts/ BEFORE
finishing. These files are the single source of truth for inter-agent collaboration.src/contracts/ for any shared types — they never redefine them.Each agent must:
npm test -- --testPathPattern=<their module>)feat(<module>): implement + unit tests)Files owned:
prisma/schema.prismaprisma/migrations/src/db/client.tssrc/db/seed.tssrc/contracts/intent.ts — PurchaseIntent types, status enum, transition event enumsrc/contracts/card.ts — VirtualCard, CardReveal typessrc/contracts/ledger.ts — LedgerEntry, Pot, LedgerEntryType enumsrc/contracts/jobs.ts — SearchIntentJob, CheckoutIntentJob payloadssrc/contracts/approval.ts — ApprovalDecision, PolicyResult typessrc/contracts/audit.ts — AuditEvent typesrc/contracts/index.ts — re-exports everythingtests/unit/db/ — schema and seed testsPrisma models to define:
User — id, email, mainBalance, maxBudgetPerIntent, merchantAllowlist, mccAllowlist, createdAtPurchaseIntent — id, userId, query, maxBudget, currency, status (enum), metadata (JSON), idempotencyKey, createdAt, updatedAt, expiresAtVirtualCard — id, intentId, stripeCardId, last4, revealedAt (nullable), frozenAt, cancelledAt, createdAtLedgerEntry — id, userId, intentId, type (RESERVE|SETTLE|RETURN), amount, currency, createdAtPot — id, userId, intentId, reservedAmount, settledAmount, status (ACTIVE|SETTLED|RETURNED), createdAt, updatedAtApprovalDecision — id, intentId, decision (APPROVED|DENIED), actorId, reason, createdAtAuditEvent — id, intentId, actor, event, payload (JSON), createdAtIdempotencyRecord — id, key, responseBody (JSON), createdAtContracts to publish (TypeScript, not just Prisma):
IntentStatus, LedgerEntryType, PotStatus, ApprovalDecisionTypeIntentEvent (INTENT_CREATED, QUOTE_RECEIVED, APPROVAL_REQUESTED, USER_APPROVED, USER_DENIED, CARD_ISSUED, CHECKOUT_STARTED, CHECKOUT_SUCCEEDED, CHECKOUT_FAILED, INTENT_EXPIRED)IOrchestrator, ICardService, IApprovalService, ILedgerService, IQueueProducerTests required:
Commit when done: feat(schema): prisma models, db client, and shared contracts
Files owned:
src/app.tssrc/server.tssrc/api/routes/intents.tssrc/api/routes/approvals.tssrc/api/routes/agent.tssrc/api/routes/webhooks.tssrc/api/routes/debug.tssrc/api/middleware/auth.tssrc/api/middleware/idempotency.tssrc/api/validators/ (Zod schemas — import types from src/contracts/)src/config/env.tstests/unit/api/ — middleware and validator teststests/integration/api/ — route-level tests with mocked servicesRoute surface:
External (Telegram later):
POST /v1/intents — create intent; requires X-Idempotency-KeyPOST /v1/approvals/:intentId/decision — user approve/deny; requires X-Idempotency-KeyGET /v1/intents/:intentId — get intent + current statusWorker-facing (requires X-Worker-Key header):
POST /v1/agent/quote — worker posts quote for a SEARCHING intentPOST /v1/agent/result — worker posts checkout resultGET /v1/agent/card/:intentId — one-time card reveal (fails if already revealed)Webhooks:
POST /v1/webhooks/stripe — Stripe event receiver; verify signature before processingDebug/Observability:
GET /v1/debug/intents — list all intents with status and timestampsGET /v1/debug/jobs — list BullMQ queue depths and recent job statusesGET /v1/debug/ledger/:userId — full ledger history for a userGET /v1/debug/audit/:intentId — full audit trail for an intentAuth rules:
X-Worker-Key middleware: compare header against WORKER_API_KEY env varstripe.webhooks.constructEvent with STRIPE_WEBHOOK_SECRET// TODO: add user auth comment)Idempotency middleware:
POST with X-Idempotency-Key: check IdempotencyRecord in DBTests required:
X-Worker-KeyCommit when done: feat(api): fastify gateway, routes, auth, idempotency, validators
Files owned:
src/orchestrator/stateMachine.tssrc/orchestrator/transitions.tssrc/orchestrator/intentService.tstests/unit/orchestrator/ — state machine transition teststests/integration/orchestrator/ — full lifecycle with real DBResponsibilities:
IntentStatus, IntentEvent from src/contracts/transitionIntent(intentId, event, payload?):
IllegalTransitionError if not)PurchaseIntent.status in DBAuditEvent for every transitionUSER_APPROVED → call ICardService.issueVirtualCard()CARD_ISSUED → call IQueueProducer.enqueueCheckout()CHECKOUT_SUCCEEDED or CHECKOUT_FAILED → call ILedgerService.settleIntent() or .returnIntent()getIntentWithHistory(intentId) — returns intent + all AuditEvents ordered by createdAtLegal transition table:
RECEIVED + INTENT_CREATED → SEARCHING
SEARCHING + QUOTE_RECEIVED → QUOTED
QUOTED + APPROVAL_REQUESTED → AWAITING_APPROVAL
AWAITING_APPROVAL + USER_APPROVED → APPROVED
AWAITING_APPROVAL + USER_DENIED → DENIED
APPROVED + CARD_ISSUED → CARD_ISSUED
CARD_ISSUED + CHECKOUT_STARTED → CHECKOUT_RUNNING
CHECKOUT_RUNNING + CHECKOUT_SUCCEEDED → DONE
CHECKOUT_RUNNING + CHECKOUT_FAILED → FAILED
<any active> + INTENT_EXPIRED → EXPIRED
Tests required:
IllegalTransitionErrorAuditEvent is written on every transitionCommit when done: feat(orchestrator): state machine, transitions, audit logging
Before writing any code, fetch and read these Stripe documentation pages in order:
Also read .claude/rules/stripe.md for project-specific gotchas (raw body for webhooks,
card number expansion, PAN storage rules). Only proceed to implementation after reading all four pages.
Files owned:
src/payments/stripeClient.tssrc/payments/cardService.tssrc/payments/webhookHandler.tssrc/payments/spendingControls.tstests/unit/payments/ — mocked Stripe SDK teststests/integration/payments/ — Stripe test mode against real API (skipped in CI without key)IMPORTANT — security rules:
VirtualCard DB record stores only: stripeCardId, last4, intentIdrevealCard) is destructive: sets revealedAt, throws on second callCard issuance (cardService.ts):
issueVirtualCard(intentId, amount, currency, options?):
spending_controls from buildSpendingControls() helperstripeCardId + last4 to VirtualCard DB recordrevealCard(intentId):
VirtualCard, throw CardAlreadyRevealedError if revealedAt is setrevealedAt = now() in DBfreezeCard(intentId) / cancelCard(intentId) — kill switch, updates DB + StripeSpending controls (spendingControls.ts):
buildSpendingControls(amount, currency, mccAllowlist?) → Stripe spending_controls objectcancellation_reason TTL via metadata (actual expiry handled by Orchestrator)Webhook handler (webhookHandler.ts):
handleStripeEvent(rawBody, signature):
stripe.webhooks.constructEvent(); throw on invalidissuing_authorization.request → log to AuditEvent, approve in test modeissuing_authorization.created → log authorized amountissuing_transaction.created → log final transaction amountTests required:
issueVirtualCard calls Stripe SDK with correct spending controls (mock Stripe)revealCard throws on second callbuildSpendingControls produces correct Stripe object for various inputsCommit when done: feat(payments): stripe issuing, card service, webhook handler
Files owned:
src/policy/policyEngine.tssrc/approval/approvalService.tssrc/ledger/ledgerService.tssrc/ledger/potService.tstests/unit/policy/tests/unit/approval/tests/unit/ledger/tests/integration/ledger/ — DB-backed pot lifecycle testPolicy engine (policyEngine.ts):
PolicyResult from src/contracts/evaluateIntent(intent, user) → PolicyResult { allowed, reason? }:
amount <= user.maxBudgetPerIntent (default $500)AuditEvent regardless of outcomeApproval service (approvalService.ts):
requestApproval(intentId) → transitions intent to AWAITING_APPROVALrecordDecision(intentId, decision, actorId):
ApprovalDecision recordintentId as idempotency key (second call replays first result)ILedgerService.reserveForIntent() then orchestrator transitionLedger service (ledgerService.ts, potService.ts):
reserveForIntent(userId, intentId, amount):
user.mainBalance >= amount (throw InsufficientFundsError otherwise)Pot (ACTIVE) + LedgerEntry (RESERVE)mainBalance, records potBalance on PotsettleIntent(intentId, actualAmount):
LedgerEntry (SETTLE)reservedAmount - actualAmount surplus to mainBalancereturnIntent(intentId):
mainBalance (for FAILED / DENIED / EXPIRED)LedgerEntry (RETURN), Pot → RETURNEDTests required:
recordDecision is idempotent (second call returns first result)reserveForIntent throws on insufficient balancesettleIntent returns correct surplusCommit when done: feat(policy-ledger): policy engine, approval service, monzo pot simulation
Role: BullMQ infrastructure + a runnable local stub that simulates OpenClaw, making the full backend testable end-to-end without a real agent.
Files owned:
src/queue/queues.tssrc/queue/jobTypes.ts (import payload types from src/contracts/)src/queue/producers.tssrc/worker/processors/searchProcessor.tssrc/worker/processors/checkoutProcessor.tssrc/worker/stubWorker.tssrc/config/redis.tstests/unit/queue/tests/integration/queue/ — enqueue + consume round-trip testQueue setup:
search-queue and checkout-queue as BullMQ Queue instancesREDIS_URL env varProducers (producers.ts):
enqueueSearch(intentId, payload: SearchIntentJob) — jobId = intentId (deduplication)enqueueCheckout(intentId, payload: CheckoutIntentJob) — jobId = intentIdStub worker (stubWorker.ts) — runnable as npx ts-node src/worker/stubWorker.ts:
Worker on checkout-queuePOST /v1/agent/result
with X-Worker-Key header and a success payloadSearch stub (searchProcessor.ts):
Worker on search-queuePOST /v1/agent/quote with a fake quote
(merchant: "Amazon UK", url: "https://amazon.co.uk/stub", price: matches job budget)Tests required:
enqueueSearch / enqueueCheckout call BullMQ Queue.add with correct jobId and payloadCommit when done: feat(queue): bullmq queues, producers, stub worker
Role: Owns all cross-module integration tests and the full happy-path E2E trace. Does NOT modify implementation files — only adds test files and fixes any import/type issues.
Files owned:
tests/integration/e2e/happyPath.test.tstests/integration/e2e/errorPaths.test.tstests/integration/e2e/stripeWebhook.test.tsTests required:
Happy path (full state machine trace):
POST /v1/intents → assert status RECEIVED, job enqueued on search-queuePOST /v1/approvals/:id/decision (APPROVED) → assert pot reserved, status APPROVEDError paths:
X-Worker-Key → 401 on all /v1/agent/* routesStripe webhook:
Commit when done: test(e2e): full happy path, error paths, webhook verification
import { ... } from '@/contracts' (path alias set by Agent 1)any types — use contracts or unknown with type guards{ level, message, intentId?, error? } — no raw console.log@types/jest; use jest.mock() for external dependencies (Stripe SDK, HTTP)npm test -- --testPathPattern=<module>DATABASE_URL=postgresql://postgres:postgres@localhost:5432/agentpay
REDIS_URL=redis://localhost:6379
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
WORKER_API_KEY=local-dev-worker-key
PORT=3000
Local Stripe webhook forwarding:
stripe listen --forward-to localhost:3000/v1/webhooks/stripe