ワンクリックで
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