<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raj Mohamed</title>
    <description>The latest articles on DEV Community by Raj Mohamed (@raj_mohamed_3de1e4c27eacc).</description>
    <link>https://dev.to/raj_mohamed_3de1e4c27eacc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3323039%2F814a6955-70ac-480b-b087-af2131efefed.jpg</url>
      <title>DEV Community: Raj Mohamed</title>
      <link>https://dev.to/raj_mohamed_3de1e4c27eacc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raj_mohamed_3de1e4c27eacc"/>
    <language>en</language>
    <item>
      <title>AI System Design for Production</title>
      <dc:creator>Raj Mohamed</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:11:59 +0000</pubDate>
      <link>https://dev.to/raj_mohamed_3de1e4c27eacc/ai-system-design-for-production-54bl</link>
      <guid>https://dev.to/raj_mohamed_3de1e4c27eacc/ai-system-design-for-production-54bl</guid>
      <description>&lt;h2&gt;
  
  
  Your Chatbot Demo Works. That Doesn't Mean It's Ready for 10,000 Users.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  An architect's walkthrough of what has to exist around the LLM before you ship an AI assistant to real customers
&lt;/h3&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkov0x4fooh968z3vb0kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkov0x4fooh968z3vb0kl.png" alt="IMAGE: 01-hero-iceberg.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The LLM call is the visible part. Almost everything that decides whether you survive launch week sits below it.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Picture this. An engineer at an e-commerce company — let's call it BlipKart — has built a chatbot. It answers questions, looks up products and even walks a customer through placing an order. The demo goes well. The CEO is impressed and asks for one thing: &lt;em&gt;put it in front of 10,000 users by Monday.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's a familiar moment, and it hides a trap. A demo proves that a model can produce a good answer when a friendly person asks a friendly question. Production asks a different question: can the &lt;strong&gt;system&lt;/strong&gt; keep producing safe, correct, affordable answers when thousands of strangers use it at once, some of them trying to break it, while your model provider has a bad day?&lt;/p&gt;

&lt;p&gt;Calling an LLM API is the easy part. Running a dependable product around it is the job. After fifteen years designing payments, cards and banking platforms — where "it worked in the demo" has never once been an acceptable sign-off — I find the same disciplines apply almost one-for-one to AI systems. Here is how I'd take BlipKart from demo to production.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Start with requirements you can measure
&lt;/h2&gt;

&lt;p&gt;Before drawing a single box, separate two conversations that teams constantly blur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-level design&lt;/strong&gt; is the map: which major components exist and how they talk. For BlipKart that's an API gateway, an intent router, a cache, a retrieval-augmented generation (RAG) pipeline, a model gateway and the commerce APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low-level design&lt;/strong&gt; is the inside of each box: vector-store replicas and regions, chunk sizes, schemas, prompt templates, the classes that implement them. Mixing the two levels in one review is how a team spends an hour debating chunk size while nobody notices there is no authentication layer.&lt;/p&gt;

&lt;p&gt;Then write the requirements down in two lists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functional&lt;/strong&gt; — what the bot must do: answer FAQs, track orders, process refunds, hand certain conversations to a human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-functional&lt;/strong&gt; — how well it must do it, with numbers stakeholders sign off on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;P95 latency under &lt;strong&gt;3 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Monthly spend within &lt;strong&gt;₹3 lakh&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero&lt;/strong&gt; PII leaks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;99.9%&lt;/strong&gt; availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those numbers aren't written down, every later trade-off becomes an argument about opinions instead of a check against a target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do the capacity maths — and don't trust the average
&lt;/h3&gt;

&lt;p&gt;10,000 users × 5 messages a day = 50,000 messages. Spread evenly across 86,400 seconds, that's roughly &lt;strong&gt;0.58 requests per second&lt;/strong&gt;. It sounds trivial. It's also misleading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7enoet8r50pg2naolsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7enoet8r50pg2naolsw.png" alt="03-capacity-average-vs-peak.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same 50,000 messages, very different load. Even a gentle evening peak is more than double the daily average — and a sale campaign or push notification can be far worse.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Traffic clusters: lunch breaks, evenings, the minute a promotional push lands. Then add retries, multi-turn conversations and tool calls that each fan out to several model invocations. Size for the &lt;strong&gt;peak minute&lt;/strong&gt;, not the day, and convert messages into &lt;strong&gt;tokens per month&lt;/strong&gt; so you can talk to your provider about rate limits and reserved capacity &lt;em&gt;before&lt;/em&gt; launch, not during an incident.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Protect customers at the boundary
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ayop8lwqggz5ftuht8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ayop8lwqggz5ftuht8y.png" alt="02-reference-architecture.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A reference shape for the system. Everything inside the trust boundary runs before a single token is generated.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticate before any account action
&lt;/h3&gt;

&lt;p&gt;"Refund my last order" means nothing until the system knows &lt;em&gt;whose&lt;/em&gt; last order. Identity has to be established with proper standards — OIDC, OAuth-based sign-in, SSO — using an identity provider such as Keycloak (or whatever your enterprise already runs).&lt;/p&gt;

&lt;p&gt;One point I'd push harder than most write-ups do: &lt;strong&gt;authorisation must live in the tools, not in the prompt.&lt;/strong&gt; The order-lookup tool should derive the customer ID from the verified token, never from text the model passes in. If the model can be talked into asking for "order 4471 for customer 9902", the only thing that should stop it is the API refusing — not the model's good manners.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let the gateway absorb abuse
&lt;/h3&gt;

&lt;p&gt;An API gateway is the right place to enforce per-user request limits and daily or monthly token budgets, before traffic reaches either your application or the LLM. Pair it with a web application firewall and load balancing. Cloudflare, NGINX, Kong, Apigee or your cloud provider's native gateway can all do this. Tune the limits to protect the provider and your bill without punishing a genuine customer who asks five follow-up questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check what goes in — and what comes out
&lt;/h3&gt;

&lt;p&gt;Prompt injection is not theoretical. There's a widely reported case of a car dealership's chatbot appearing to agree to sell a vehicle worth around $70,000 for $1 after a user instructed it to agree with everything. Whatever the legal outcome of such cases, the design lesson is clear: &lt;strong&gt;define, in advance, what the bot is never allowed to promise.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input guardrails&lt;/strong&gt; flag disallowed or manipulative requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output guardrails&lt;/strong&gt; catch PII, prices or commitments the bot has no authority to make before the response reaches the customer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Legal and compliance should own the list of prohibited responses. Engineering owns enforcing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Route every request to the right place
&lt;/h2&gt;

&lt;p&gt;Not every message deserves a large language model. A greeting, an order-status question, a refund request and a policy question are four different workloads.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;intent router&lt;/strong&gt; sits in the middle of the design and picks the path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Greetings and thanks → a fixed response&lt;/li&gt;
&lt;li&gt;Order status, prices, refunds → commerce APIs and tools&lt;/li&gt;
&lt;li&gt;Policy and how-to questions → RAG&lt;/li&gt;
&lt;li&gt;Ambiguous sales or support questions → a classifier, and only then an LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cache what's stable. Fetch what's live.
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fms898jgylkbh9wb0yz7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fms898jgylkbh9wb0yz7q.png" alt="05-cache-vs-fresh.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Semantic caching is a cost win for stable answers and a correctness bug for anything live or personal.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Semantic caching matches questions by meaning, so "how do I pay with UPI?" and "can I use UPI to pay?" can share one answer. That saves real money on repetitive FAQs.&lt;/p&gt;

&lt;p&gt;It is also a sharp tool. Two rules I'd treat as non-negotiable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never cache live facts or account actions.&lt;/strong&gt; Prices come from the pricing API. Order status comes from the order service. A return request triggers a tool, not a remembered answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never let a cache key cross a customer boundary.&lt;/strong&gt; A cached answer that contains anything personal is a PII leak waiting to happen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Give every cached entry a TTL and an explicit invalidation trigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep RAG content honest
&lt;/h3&gt;

&lt;p&gt;Policy PDFs and troubleshooting guides get chunked, embedded and indexed in a vector store — Qdrant, Chroma, Pinecone, pgvector and others. The failure mode isn't retrieval quality on day one; it's &lt;strong&gt;staleness on day ninety&lt;/strong&gt;, when the return policy changed and the bot is still quoting the old one.&lt;/p&gt;

&lt;p&gt;Wire &lt;strong&gt;change data capture&lt;/strong&gt; into the content source. When a document changes, re-index only the affected chunks and evict the cache entries built on them. No nightly "re-index everything" job, and no stale answers lingering for a week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Know when to hand over to a human
&lt;/h3&gt;

&lt;p&gt;A customer disputing a charge or about to cancel a membership needs judgement and empathy, not a generated paragraph. Decide up front which intents the AI must &lt;strong&gt;never&lt;/strong&gt; handle alone. And if the bot is allowed to offer something with commercial value — a retention discount, a goodwill credit — write explicit rules for when, how much and how often.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Control cost without lowering quality
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Minimum sufficient intelligence
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpz4lnjlvcrinjc5oujgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpz4lnjlvcrinjc5oujgg.png" alt="04-minimum-sufficient-intelligence.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Climb only as high as the task needs. Every rung up costs more and behaves less predictably.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the principle I'd put on the wall: &lt;strong&gt;use the least complex mechanism that meets the quality bar.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Thanks!" needs a rule, not a model.&lt;/li&gt;
&lt;li&gt;"Where's my order?" needs an API call, not a model.&lt;/li&gt;
&lt;li&gt;Sorting a known set of request types is a job for a traditional ML classifier.&lt;/li&gt;
&lt;li&gt;A standard return-policy answer is fine on a small, cheap LLM.&lt;/li&gt;
&lt;li&gt;A split-shipment refund with a partial return and a voucher? That may genuinely need a larger reasoning model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rules are cheap, fast and predictable. Models are flexible. Spend flexibility only where you need it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Route models by task, and prove it with evals
&lt;/h3&gt;

&lt;p&gt;Model routing sends each request to the model best suited to it. The choice shouldn't be made on brand or parameter count — build an evaluation set from real BlipKart conversations and score candidates on accuracy, latency, cost and policy compliance. Re-run it every time you change a prompt or a model version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make spend visible before it becomes a surprise
&lt;/h3&gt;

&lt;p&gt;If the monthly bill quietly drifts from ₹3 lakh to ₹6 lakh, you want to find out on day eight, not in the invoice. Set token budgets per user or account, track spend &lt;strong&gt;per model and per intent&lt;/strong&gt;, and alert as you approach thresholds. Review regularly which intents actually justify the expensive model.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Build for spikes and outages
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8w3j073hjo236yk269n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8w3j073hjo236yk269n.png" alt="06-resilience-gateway-queue.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Queues absorb bursts; the model gateway hides provider failures behind a tested fallback.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Put a model gateway between you and your providers
&lt;/h3&gt;

&lt;p&gt;An AI/model gateway keeps provider keys out of application code, centralises rate limits and token budgets, and can fail over to a second provider when the primary returns errors or throttles you. LiteLLM and Vercel AI Gateway are two examples.&lt;/p&gt;

&lt;p&gt;The catch: &lt;strong&gt;a fallback you haven't tested is not a fallback.&lt;/strong&gt; Prompts tuned for one model often behave differently on another — different formatting, different refusal behaviour, different tool-calling quirks. Run your evaluation suite against the fallback provider too, and load-test it at the traffic it would actually receive during a failover.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queue, throttle, scale
&lt;/h3&gt;

&lt;p&gt;When requests arrive faster than you can serve them, queues smooth the curve. Backpressure and throttling slow intake before you overwhelm the provider. Kafka or RabbitMQ can hand long-running work to background workers so the chat API doesn't block. Load balancing and autoscaling add and remove compute as traffic moves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming&lt;/strong&gt; responses over server-sent events shows text as it's generated. It doesn't make the total answer faster — but it dramatically reduces the time a customer stares at a spinner, which is what they actually perceive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make releases and recovery boring
&lt;/h3&gt;

&lt;p&gt;Infrastructure as code (Terraform) and CI/CD (GitHub Actions, GitLab, Jenkins) make deployments repeatable and reviewable. For AI systems I'd add one more gate: &lt;strong&gt;an automated evaluation run that blocks the deploy if quality drops.&lt;/strong&gt; Every release needs a tested rollback path, and the team should rehearse recovery before a real outage forces them to improvise.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Measure health — and correctness
&lt;/h2&gt;

&lt;p&gt;A green uptime dashboard doesn't mean the bot is right. A chatbot can be fast, available and confidently wrong.&lt;/p&gt;

&lt;p&gt;Track the operational signals — P95 latency, token usage, spend, error rates — &lt;strong&gt;and&lt;/strong&gt; the behavioural ones: did it call the right tool? Did the refund flow complete? Did the answer match current policy? Tools like Datadog, New Relic, LangSmith and MLflow each cover parts of this.&lt;/p&gt;

&lt;p&gt;Set alerts on the targets you agreed in step one: latency above 3 seconds, spend crossing budget, guardrail trigger rates spiking. Route them by severity — email for trends, a page for an outage. Keep end-to-end traces so an engineer can see exactly which hop failed. And keep re-checking behaviour after &lt;strong&gt;every&lt;/strong&gt; change to prompts, models, policies or integrations, because each one can shift answers in ways no unit test will catch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The production-readiness checklist
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgm3khpxxzm8qec78y3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgm3khpxxzm8qec78y3o.png" alt="07-production-readiness-checklist.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Twelve questions to answer before opening access to real customers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;BlipKart's prototype is a good start. But production readiness was never about the model — it's about the system around it.&lt;/p&gt;

&lt;p&gt;Before you open the doors to 10,000 users, confirm the requirements and the peak-traffic maths. Check authentication, abuse controls, guardrails, data freshness, human handoffs, cost limits, fallbacks, scaling, monitoring and recovery. Then run a load test at the expected peak and, just as importantly, &lt;strong&gt;prove the system fails safely&lt;/strong&gt; — that when something breaks, the customer gets a polite handoff, not a wrong refund.&lt;/p&gt;

&lt;p&gt;Every one of these choices is a trade-off between risk, quality, latency and budget. There's no universal stack. But there is a universal question worth asking before Monday:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If this goes wrong at 8 pm on launch day, what happens next — and who finds out first: you, or your customers?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Raj Mohamad Sabibullah, a Senior Solution Architect working across payments, cards, banking platforms and applied AI. If you're taking an AI assistant from prototype to production, I'd like to hear what tripped you up — leave a response below or connect with me on LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; Artificial Intelligence · System Design · LLM · Software Architecture · Generative AI&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>production</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title># Building a STRIDE-Aware Fraud-Pattern Review Agent on Amazon Bedrock</title>
      <dc:creator>Raj Mohamed</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:08:13 +0000</pubDate>
      <link>https://dev.to/raj_mohamed_3de1e4c27eacc/-building-a-stride-aware-fraud-pattern-review-agent-on-amazon-bedrock-df5</link>
      <guid>https://dev.to/raj_mohamed_3de1e4c27eacc/-building-a-stride-aware-fraud-pattern-review-agent-on-amazon-bedrock-df5</guid>
      <description>&lt;p&gt;A step-by-step build: from model access to a deployed agent, with every console screen along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raj Mohamad Sabibullah&lt;/strong&gt; · Senior Solution Architect&lt;/p&gt;

&lt;p&gt;I spend most of my week in the unglamorous middle layer of a digital bank build: a card management system on one side, a core banking system on the other, Kafka topics carrying every authorization and reversal in between, and a security team that wants a threat model for anything that so much as reads a transaction record. It is not the part of the stack that gets a conference talk. But it is exactly the part where agentic AI turned out to be more useful than I expected — not as a chatbot bolted onto a mobile app, but as a colleague sitting inside the fraud-ops workflow, reading runbooks faster than any analyst can, and never seeing a live PAN.&lt;/p&gt;

&lt;p&gt;This post is the build log I wish I'd had going in: every step from requesting model access to a working CLI invocation, in order, with the console screen at each step. It's long on purpose — a previous, shorter version of this post got fair feedback that it skipped the "how," so this one doesn't skip anything. Account IDs, transaction references and internal system names below are illustrative, not real; the steps, screens and configuration are the real sequence I used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we're building&lt;/strong&gt; An agent that takes a flagged transaction reference from a cards-ops analyst, classifies it against a STRIDE-based fraud taxonomy, cites the PCI DSS 4.0 control it relied on, and returns a risk rating — without ever seeing or returning an unmasked card number.&lt;/p&gt;

&lt;p&gt;On this page&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Request model access&lt;/li&gt;
&lt;li&gt;Stage the knowledge source in S3&lt;/li&gt;
&lt;li&gt;Create the Knowledge Base&lt;/li&gt;
&lt;li&gt;Create the IAM execution role&lt;/li&gt;
&lt;li&gt;Write the Lambda action groups&lt;/li&gt;
&lt;li&gt;Create the agent&lt;/li&gt;
&lt;li&gt;Attach the action group schema&lt;/li&gt;
&lt;li&gt;Create the guardrail&lt;/li&gt;
&lt;li&gt;Prepare and test the agent&lt;/li&gt;
&lt;li&gt;Create an alias, promote a version&lt;/li&gt;
&lt;li&gt;Codify in CDK and deploy&lt;/li&gt;
&lt;li&gt;Invoke from the CLI / your app&lt;/li&gt;
&lt;li&gt;Reference architecture&lt;/li&gt;
&lt;li&gt;Mapping STRIDE to cards&lt;/li&gt;
&lt;li&gt;What broke, what I'd change&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  0 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before touching the console, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account with Amazon Bedrock available in your target region (I used &lt;code&gt;eu-central-1&lt;/code&gt; for data-residency reasons — check what your own compliance scope requires before picking a region).&lt;/li&gt;
&lt;li&gt;IAM permissions to create roles, Lambda functions, S3 buckets, an OpenSearch Serverless collection, and Bedrock resources (agents, knowledge bases, guardrails). In a real bank environment this is almost never "AdministratorAccess" — I worked with a scoped permission set our cloud platform team provisioned for the Bedrock build.&lt;/li&gt;
&lt;li&gt;The AWS CLI v2 and the AWS CDK v2 installed locally, both authenticated against a non-production account/profile.&lt;/li&gt;
&lt;li&gt;A read-only API in front of your core-banking system (CMS, Core Banking System, or equivalent) that the agent's action group can call. &lt;strong&gt;The agent never talks to the core-banking systems directly&lt;/strong&gt; — it always goes through this proxy, which is the enforcement point for "read-only, no PII."&lt;/li&gt;
&lt;li&gt;Your fraud runbooks and control-mapping documents, as PDFs or text files, ready to upload as the knowledge source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in place, the build runs in the order below — each step depends on the one before it.&lt;/p&gt;




&lt;h2&gt;
  
  
  1 Request model access
&lt;/h2&gt;

&lt;p&gt;Bedrock model access is granted per account, per region, and it is not on by default. This trips up almost everyone the first time — an agent creation will fail silently referencing a model you haven't been granted.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Amazon Bedrock → Model access&lt;/strong&gt; and request access to the models you need. For this build that's Claude 3.7 Sonnet (the agent's reasoning model) and Titan Text Embeddings V2 (for the knowledge base).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fahirr6l1waptqrhm7ms1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fahirr6l1waptqrhm7ms1.png" alt="Amazon Bedrock Model access console showing Anthropic Claude and Amazon Titan models with access status" width="799" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Model access is a one-time, per-region grant. Approval for Anthropic models is usually instant; some providers require a use-case form.&lt;/p&gt;

&lt;p&gt;Access typically activates within a few minutes for Anthropic and Amazon models. Confirm the status shows &lt;strong&gt;Access granted&lt;/strong&gt; before moving on — every later step assumes this is done.&lt;/p&gt;




&lt;h2&gt;
  
  
  2 Stage the knowledge source in S3
&lt;/h2&gt;

&lt;p&gt;The agent's domain knowledge — STRIDE runbooks, the PCI DSS control mapping, prior incident write-ups — needs to live somewhere Bedrock can ingest it from. Create a dedicated S3 bucket and upload the source documents.&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws s3 mb s3://stride-runbooks-kb-src-eucentral1 --region eu-central-1
aws s3 cp ./runbooks/ s3://stride-runbooks-kb-src-eucentral1/ --recursive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgvenm5r6c1ktx0plhk2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgvenm5r6c1ktx0plhk2s.png" alt="Amazon S3 console showing the stride-runbooks-kb-src bucket with uploaded STRIDE runbook PDFs and a PCI DSS control-mapping CSV" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One file per STRIDE category kept retrieval clean later — a runbook that blends two categories produces a knowledge-base chunk that blends two answers.&lt;/p&gt;

&lt;p&gt;A few things that mattered here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One document per STRIDE category&lt;/strong&gt;, not one giant "fraud handbook" PDF. Chunking works section-by-section; a single 40-page PDF covering everything produces chunks that straddle unrelated topics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable versioning on the bucket&lt;/strong&gt; if your governance process requires an audit trail of what the agent was trained on at a point in time — ours does, since a control mapping can change between PCI DSS revisions.&lt;/li&gt;
&lt;li&gt;Keep this bucket in the same region as the knowledge base you create next; cross-region ingestion adds complexity you don't need for a first build.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3 Create the Bedrock Knowledge Base
&lt;/h2&gt;

&lt;p&gt;This is where the runbooks become something the agent can retrieve from. Go to &lt;strong&gt;Amazon Bedrock → Knowledge bases → Create knowledge base&lt;/strong&gt; and work through the wizard.&lt;/p&gt;

&lt;p&gt;The first two steps of the wizard (name/description, then pointing at the S3 data source) are straightforward. The step worth slowing down on is vector store configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgeguqwcbjgpvy27hr69v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgeguqwcbjgpvy27hr69v.png" alt="Bedrock Knowledge Base creation wizard, step 4 of 5, showing vector store selection, embeddings model, and chunking strategy set to fixed-size 150 tokens with 20% overlap" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chunk size was the single highest-leverage tuning knob in this whole build — more than model choice or prompt wording.&lt;/p&gt;

&lt;p&gt;Configuration choices that mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vector store: "Quick create a new vector store"&lt;/strong&gt; provisions an Amazon OpenSearch Serverless collection for you. For a first build this is the right choice — you can migrate to a self-managed collection or Aurora pgvector later if you need finer control over capacity or cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings model: Titan Text Embeddings V2&lt;/strong&gt; at 1024 dimensions. Cohere and other embedding models are available; Titan was the default and there was no reason to move off it for this use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking strategy: fixed-size, 150 tokens, 20% overlap.&lt;/strong&gt; The Bedrock default is 300 tokens. Our runbook sections are short and dense (one STRIDE category, one control, one example), and at the 300-token default the agent would occasionally retrieve a chunk that blended two adjacent categories. Dropping to 150 tokens fixed this more reliably than any prompt-level instruction did.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the wizard finishes, &lt;strong&gt;sync the data source&lt;/strong&gt; (Knowledge base → Data source → Sync) so the documents in S3 are actually embedded and indexed — creating the knowledge base does not automatically ingest the files. Sync typically takes a few minutes for a handful of PDFs; watch the sync history for failures before assuming it worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  4 Create the IAM execution role
&lt;/h2&gt;

&lt;p&gt;Every Bedrock agent runs under an IAM role that Bedrock assumes to invoke the model, retrieve from the knowledge base, and call your action-group Lambdas. This is the control that turns "an LLM agent" into "an LLM agent that provably cannot write to your core-banking system," and it's worth building before you build the agent itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtyv0i9thbw7fnvuypoc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtyv0i9thbw7fnvuypoc.png" alt="IAM console showing the BedrockAgentExecutionRole-Fraud role with a permissions boundary denying core-banking writes, five scoped policies, and a trust relationship restricted to the bedrock service principal" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The permissions boundary is the layer that matters most: even if the Lambda code changes next year to add a write path, the boundary denies it at the IAM layer regardless of what the code tries to do.&lt;/p&gt;

&lt;p&gt;Two design decisions here, not just defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A permissions boundary, not just attached policies.&lt;/strong&gt; Attached policies say what the role &lt;em&gt;can&lt;/em&gt; do; the boundary is a hard ceiling on what any policy attached to this role — now or in the future — is allowed to grant. We set ours to explicitly deny any write action against the core-banking proxy, so a future code change that tries to add a write call fails at deploy time, not in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One policy per Lambda, scoped to that Lambda's ARN&lt;/strong&gt;, rather than a single &lt;code&gt;lambda:InvokeFunction&lt;/code&gt; on &lt;code&gt;*&lt;/code&gt;. This is more setup work and it is the difference between "the agent can call three specific, reviewed functions" and "the agent can call any Lambda in the account," which is not a sentence I wanted to say to our security team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trust relationship restricts &lt;code&gt;sts:AssumeRole&lt;/code&gt; to the &lt;code&gt;bedrock.amazonaws.com&lt;/code&gt; service principal, scoped further with an &lt;code&gt;aws:SourceAccount&lt;/code&gt; condition — standard practice, worth double-checking it's actually there rather than assumed.&lt;/p&gt;




&lt;h2&gt;
  
  
  5 Write the Lambda action-group functions
&lt;/h2&gt;

&lt;p&gt;The agent doesn't touch any real system directly — every action it can take is a Lambda function behind an action group. We built three:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cms-read-proxy-fn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Looks up a transaction by reference ID, returns a masked record&lt;/td&gt;
&lt;td&gt;Read-only, scoped IAM role into the CMS read replica&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stride-classifier-fn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scores the transaction against STRIDE categories using rules + recent MSK velocity features&lt;/td&gt;
&lt;td&gt;Reads from MSK consumer group, no write access anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sns-notify-fn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publishes an alert to the cards-ops on-call topic when risk is HIGH&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sns:Publish&lt;/code&gt; on one topic only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A trimmed version of the transaction-lookup handler, showing the masking that happens before anything reaches the model:&lt;/p&gt;

&lt;p&gt;python · cms-read-proxy-fn&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import boto3

def lambda_handler(event, context):
    reference_id = event["parameters"][0]["value"]
    record = fetch_from_cms_readonly(reference_id)  # internal read-only client

    # Mask before this ever leaves the function — the agent and the
    # guardrail are a second line of defense, not the first.
    masked_pan = "•••• •••• •••• " + record["pan"][-4:]

    response_body = {
        "referenceId": reference_id,
        "amount": record["amount"],
        "currency": record["currency"],
        "maskedPan": masked_pan,
        "deviceFingerprint": record["device_fingerprint"],
        "authCount90s": record["velocity_90s"],
        "merchantCategoryCode": record["mcc"],
    }

    return {
        "messageVersion": "1.0",
        "response": {
            "actionGroup": event["actionGroup"],
            "apiPath": event["apiPath"],
            "httpMethod": event["httpMethod"],
            "httpStatusCode": 200,
            "responseBody": {"application/json": {"body": json.dumps(response_body)}},
        },
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The masking happening inside the Lambda — not left to the model or the guardrail to catch — is deliberate. Guardrails and instructions are the second and third layers; the function itself should never emit a raw PAN in the first place.&lt;/p&gt;

&lt;p&gt;Deploy these three functions however your team normally deploys Lambdas (console, SAM, CDK — we moved to CDK for this in step 11). Note each function's ARN; you'll need them for the action group and the IAM role above.&lt;/p&gt;




&lt;h2&gt;
  
  
  6 Create the agent
&lt;/h2&gt;

&lt;p&gt;With the knowledge base synced, the execution role scoped, and the Lambdas deployed, go to &lt;strong&gt;Amazon Bedrock → Agents → Create agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvye3tz1m05z3hbo86vdd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvye3tz1m05z3hbo86vdd.png" alt="Amazon Bedrock console showing the Agent builder screen for fraud-pattern-review-agent, with instructions, foundation model, action groups, knowledge base and guardrail all attached" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything the agent can reason with or act on attaches to this one resource: model, instructions, action groups, knowledge base, and guardrail.&lt;/p&gt;

&lt;p&gt;Fill in, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agent name and description&lt;/strong&gt; — &lt;code&gt;fraud-pattern-review-agent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM role&lt;/strong&gt; — select the execution role created in step 4 (or let the console generate one, then immediately tighten it to match — I'd recommend building it yourself first, since the auto-generated role is intentionally broad).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foundation model&lt;/strong&gt; — Claude 3.7 Sonnet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent instructions&lt;/strong&gt; — this is the single highest-leverage text in the whole build. Ours:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;agent instructions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a fraud-pattern review assistant for the XY Bank Cards Workstream.
Given a transaction reference, cross-check STRIDE threat categories against the
knowledge base of card-fraud runbooks, summarize risk indicators in plain
language, cite the matching control from PCI DSS 4.0 / CBE regulation where
applicable, and NEVER return raw PAN, CVV, or cardholder PII in your response.
Always call the transaction-lookup action before concluding a risk rating.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lessons baked into that wording:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State the negative constraint as its own capitalized sentence, early.&lt;/strong&gt; Buried as a passive aside, it gets ignored more often than as a standalone line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Force the tool call explicitly.&lt;/strong&gt; Without "always call the transaction-lookup action before concluding," the agent would occasionally answer from a prior turn's context instead of looking the transaction up fresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name the citation format you want.&lt;/strong&gt; Asking for "the matching control from PCI DSS 4.0 / CBE regulation where applicable" produces consistent, checkable citations — paired with contextual grounding in the guardrail (step 8), which stops the model from inventing a plausible-sounding control number it didn't actually retrieve.&lt;/p&gt;

&lt;p&gt;Save the agent. It exists now, but it can't do anything yet — no action groups, no knowledge base link, no guardrail. Those are the next three steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  7 Attach the action group and its OpenAPI schema
&lt;/h2&gt;

&lt;p&gt;An action group tells the agent what it can call and how — Bedrock uses an OpenAPI schema to generate the function-calling interface, and a Lambda function to actually execute the call.&lt;/p&gt;

&lt;p&gt;Go to the agent's &lt;strong&gt;Action groups&lt;/strong&gt; tab and create one per Lambda (&lt;code&gt;transaction-lookup&lt;/code&gt;, &lt;code&gt;stride-classifier&lt;/code&gt;, &lt;code&gt;analyst-notify&lt;/code&gt;). For each, select "Define with API schemas," point it at the corresponding Lambda ARN, and provide the schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6bjk4sv291wob642x18o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6bjk4sv291wob642x18o.png" alt="Bedrock action group editor for transaction-lookup, showing the action group type set to Define with API schemas, the Lambda function selected, and the OpenAPI schema defining the getTransaction operation" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OpenAPI schema is what lets the model decide, on its own, when to call this action and with what parameters — get the field descriptions right and the model's tool-use gets noticeably more reliable.&lt;/p&gt;

&lt;p&gt;The schema for &lt;code&gt;transaction-lookup&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;yaml · openapi schema&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openapi: 3.0.0
info:
  title: Transaction Lookup API
  version: 1.0.0
paths:
  /transaction/{referenceId}:
    get:
      summary: Retrieve a masked transaction record by reference ID
      operationId: getTransaction
      parameters:
        - name: referenceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Masked transaction record
          content:
            application/json:
              schema:
                type: object
                properties:
                  referenceId: { type: string }
                  amount: { type: number }
                  currency: { type: string }
                  maskedPan: { type: string, example: "•••• •••• •••• 4471" }
                  deviceFingerprint: { type: string }
                  authCount90s: { type: integer }
                  merchantCategoryCode: { type: string }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two practical notes: the &lt;code&gt;summary&lt;/code&gt; field on each operation matters more than you'd expect — it's what the model reads to decide &lt;em&gt;when&lt;/em&gt; to call this tool, so write it as a clear instruction, not a terse label. And repeat this for &lt;code&gt;stride-classifier&lt;/code&gt; and &lt;code&gt;analyst-notify&lt;/code&gt; with their own schemas before moving on — an agent with only one of three action groups attached will happily answer without ever invoking the other two.&lt;/p&gt;

&lt;p&gt;Then, on the agent's main page, link the &lt;strong&gt;knowledge base&lt;/strong&gt; created in step 3 under the Knowledge base section, and give it its own retrieval instructions (a short sentence telling the agent when to consult it — e.g., "Use this knowledge base to look up STRIDE categories and their matching compliance controls").&lt;/p&gt;




&lt;h2&gt;
  
  
  8 Create the guardrail
&lt;/h2&gt;

&lt;p&gt;This is the step I'd tell anyone building something similar to budget the most time for — it took longer than the agent and action groups combined, mostly in review cycles with our security team, not console clicks.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Amazon Bedrock → Guardrails → Create guardrail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9c6ac48xcfe52nrejd6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9c6ac48xcfe52nrejd6m.png" alt="Amazon Bedrock Guardrails console showing cards-pii-guardrail-v3 with sensitive information filters for PAN and CVV set to Block, denied topics, and contextual grounding thresholds of 0.75 and 0.70" width="799" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A guardrail is a separate, versioned resource from the agent — you can update the guardrail and ship a stricter policy without redeploying the agent itself.&lt;/p&gt;

&lt;p&gt;Three configuration sections did most of the work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive information filters&lt;/strong&gt; — add &lt;code&gt;CREDIT_DEBIT_CARD_NUMBER&lt;/code&gt; and &lt;code&gt;CREDIT_DEBIT_CARD_CVV&lt;/code&gt; as built-in PII types, action set to &lt;strong&gt;Block&lt;/strong&gt; (not just mask), plus a regex-based filter for our local national ID format. Blocking rather than masking here means a masked PAN can't even partially leak across a multi-turn conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Denied topics&lt;/strong&gt; — phrased as topics ("bypassing 3-D Secure or step-up authentication," "requesting core-banking service account credentials"), not keyword lists. Keyword blocklists are trivially rephrased around; topic-level denial catches the intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual grounding checks&lt;/strong&gt; — grounding threshold 0.75, relevance threshold 0.70. This is the control that rejects a generated answer when it isn't well-supported by what was actually retrieved from the knowledge base — it's what stops the model from citing a PCI DSS requirement number it half-remembers instead of one it retrieved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once created, attach the guardrail to the agent from the agent's main configuration page (visible in the step 6 screenshot above, bottom right). Guardrails are versioned independently of the agent, so tightening a filter later doesn't require touching the agent resource at all — useful when a security review asks for a stricter policy mid-project.&lt;/p&gt;




&lt;h2&gt;
  
  
  9 Prepare and test the agent
&lt;/h2&gt;

&lt;p&gt;With instructions, action groups, knowledge base, and guardrail all attached, click &lt;strong&gt;Prepare&lt;/strong&gt; on the agent (top right of the Agent builder screen) — this compiles the current draft configuration into something invocable. Then open the &lt;strong&gt;Test&lt;/strong&gt; window to run it end to end before anything touches a real alias.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/assets%2F03_agent_test_window.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/assets%2F03_agent_test_window.png" alt="Bedrock agent test window showing a chat transcript where the agent invokes the transaction-lookup and stride-classifier action groups and returns a risk rating with a masked PAN and a cited PCI DSS control" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A full round trip in the test window: two action-group calls, a STRIDE classification, a cited control, and a masked card number — all before this reaches a real alias.&lt;/p&gt;

&lt;p&gt;What to check here, beyond "did it answer":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Did it call the action group at all&lt;/strong&gt;, or answer from the knowledge base alone? The trace panel (expand any response in the test window) shows every tool invocation — use it, don't just read the final text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the PAN actually masked&lt;/strong&gt; in the response, or did the guardrail have to intervene? You want the masking to happen at the Lambda layer (step 5) and never rely on the guardrail as the only line of defense.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the cited control real?&lt;/strong&gt; Cross-check a few citations against your actual PCI DSS mapping document. If grounding is working, these should be reliably accurate; if they aren't, revisit chunk size before touching the guardrail thresholds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Iterate on the instructions and chunking here — this loop is fast (no redeploy needed) and it's where most of the actual tuning happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  10 Create an alias and promote a version
&lt;/h2&gt;

&lt;p&gt;The agent's &lt;code&gt;DRAFT&lt;/code&gt; state is for testing only. To integrate it into anything real, create a &lt;strong&gt;version&lt;/strong&gt; (an immutable snapshot of the prepared configuration) and point an &lt;strong&gt;alias&lt;/strong&gt; at it — your application code calls the alias, not the draft, so you can promote a new version without changing any calling code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fruznuw8nusv0oi70tfrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fruznuw8nusv0oi70tfrp.png" alt="Bedrock Aliases and versions screen showing a prod-v1 alias pointing at version 3, a staging alias pointing at a newer draft version, and the auto-generated test alias" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;prod-v1 stays pinned to a reviewed, immutable version. Testing a new instruction wording happens against staging first.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Aliases → Create alias&lt;/strong&gt;, name it (&lt;code&gt;prod-v1&lt;/code&gt;), and either create a new version from the current draft or point it at an existing one. Keep a &lt;code&gt;staging&lt;/code&gt; alias around pointed at whatever draft you're actively iterating on — it lets you validate a change against the same integration path production uses, without touching &lt;code&gt;prod-v1&lt;/code&gt; until you're ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  11 Codify everything in CDK and deploy
&lt;/h2&gt;

&lt;p&gt;Once the console configuration behaves the way you want, capture it as infrastructure-as-code so it ships through the same pipeline as everything else in the workstream, with a clean audit trail for the security sign-off — no console drift between what was reviewed and what's running.&lt;/p&gt;

&lt;p&gt;typescript · cdk stack&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const guardrail = new bedrock.CfnGuardrail(this, 'CardsPiiGuardrail', {
  name: 'cards-pii-guardrail-v3',
  blockedInputMessaging: 'This request cannot be processed under current policy.',
  blockedOutputsMessaging: 'This response was withheld by the cards data guardrail.',
  sensitiveInformationPolicyConfig: {
    piiEntitiesConfig: [
      { type: 'CREDIT_DEBIT_CARD_NUMBER', action: 'BLOCK' },
      { type: 'CREDIT_DEBIT_CARD_CVV', action: 'BLOCK' },
    ],
  },
  contextualGroundingPolicyConfig: {
    filtersConfig: [
      { type: 'GROUNDING', threshold: 0.75 },
      { type: 'RELEVANCE', threshold: 0.70 },
    ],
  },
});

const agent = new bedrock.CfnAgent(this, 'FraudPatternAgent', {
  agentName: 'fraud-pattern-review-agent',
  foundationModel: 'anthropic.claude-3-7-sonnet-20250219-v1:0',
  instruction: fs.readFileSync('./agent-instructions.txt', 'utf-8'),
  guardrailConfiguration: {
    guardrailIdentifier: guardrail.attrGuardrailId,
    guardrailVersion: guardrail.attrVersion,
  },
  agentResourceRoleArn: agentExecutionRole.roleArn,
});

const alias = new bedrock.CfnAgentAlias(this, 'ProdAlias', {
  agentId: agent.attrAgentId,
  agentAliasName: 'prod-v1',
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrap once per account/region if you haven't already, then deploy:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk bootstrap aws://104829xxxxxx/eu-central-1 --profile xybank-nonprod
cdk deploy FraudPatternAgentStack --profile xybank-nonprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhl9qd3wnsmjd7wduesrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhl9qd3wnsmjd7wduesrq.png" alt="Terminal showing a successful cdk deploy of FraudPatternAgentStack with IAM role, S3 bucket, Lambda functions, guardrail, knowledge base, agent and alias all created, followed by stack outputs and an invoke-agent CLI call" width="799" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same agent, same configuration, now deployed through CDK — IAM role, Lambda action groups, guardrail, knowledge base and alias all in one stack.&lt;/p&gt;

&lt;p&gt;The stack outputs (&lt;code&gt;AgentId&lt;/code&gt;, &lt;code&gt;AgentAliasId&lt;/code&gt;, &lt;code&gt;KnowledgeBaseId&lt;/code&gt;) are what your calling application needs — capture them in your deployment pipeline's output rather than hardcoding them, since a version promotion can change the alias ID.&lt;/p&gt;




&lt;h2&gt;
  
  
  12 Invoke the agent from the CLI / your app
&lt;/h2&gt;

&lt;p&gt;With an alias live, the agent is callable via the &lt;code&gt;bedrock-agent-runtime&lt;/code&gt; API — from the CLI for a smoke test, or from your cards-ops console backend for the real integration.&lt;/p&gt;

&lt;p&gt;bash · smoke test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws bedrock-agent-runtime invoke-agent \
  --agent-id FRAUD7X2QK9 \
  --agent-alias-id TSTALIASID \
  --session-id test-001 \
  --input-text "Review txn XYB-2026-88213910" \
  --profile xybank-nonprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the actual application integration, the same call goes through the AWS SDK from the Lambda orchestrator behind API Gateway (see the architecture diagram below) — the CLI call above is purely for verifying the deployed alias works before wiring up the real request path.&lt;/p&gt;

&lt;p&gt;python · orchestrator&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3

client = boto3.client("bedrock-agent-runtime", region_name="eu-central-1")

response = client.invoke_agent(
    agentId="FRAUD7X2QK9",
    agentAliasId="PRODALIASID",
    sessionId=session_id,
    inputText=analyst_query,
)

for event in response["completion"]:
    if "chunk" in event:
        print(event["chunk"]["bytes"].decode())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the full path — model access, knowledge base, IAM boundary, action groups, agent, guardrail, alias, IaC, invocation. Everything from here is tuning.&lt;/p&gt;




&lt;h2&gt;
  
  
  13 Reference architecture
&lt;/h2&gt;

&lt;p&gt;Zooming back out, here's how all the pieces built above fit together:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4wihv6v44u6aywtzcaks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4wihv6v44u6aywtzcaks.png" alt="Reference architecture diagram showing the Bedrock Agent, Knowledge Base, Guardrails, action groups, and the read-only path into the core-banking systems and MSK" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent orchestrates through Lambda action groups; nothing writes back to the core-banking system, and PII never leaves the guardrail boundary.&lt;/p&gt;

&lt;p&gt;API Gateway and the Lambda orchestrator in front of the agent (left side of the diagram) handle request-level auth against our existing identity-provider session before anything reaches Bedrock — that layer isn't part of the Bedrock build itself, but it's what makes "who called this agent and were they authorized" answerable, which matters as much to a security review as anything inside Bedrock does.&lt;/p&gt;




&lt;h2&gt;
  
  
  14 Mapping STRIDE onto a cards workflow
&lt;/h2&gt;

&lt;p&gt;STRIDE was built for software threat modeling, not fraud triage, so we adapted rather than adopted it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;STRIDE category&lt;/th&gt;
&lt;th&gt;What it looks like in a card transaction&lt;/th&gt;
&lt;th&gt;Primary control we cite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;S&lt;/strong&gt;poofing&lt;/td&gt;
&lt;td&gt;Card-not-present transaction from a new device fingerprint, no prior history with the payee&lt;/td&gt;
&lt;td&gt;PCI DSS 4.0 Req 8.4.2 — MFA for CNP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;T&lt;/strong&gt;ampering&lt;/td&gt;
&lt;td&gt;Amount or MCC mismatch between authorization and clearing message&lt;/td&gt;
&lt;td&gt;ISO 8583 field-level integrity checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;R&lt;/strong&gt;epudiation&lt;/td&gt;
&lt;td&gt;Multiple rapid authorizations with no matching customer-initiated event in the channel logs&lt;/td&gt;
&lt;td&gt;Non-repudiation logging, regulatory circular on transaction logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;I&lt;/strong&gt;nformation disclosure&lt;/td&gt;
&lt;td&gt;Any flow where the agent itself would need to return unmasked PAN/CVV&lt;/td&gt;
&lt;td&gt;Bedrock Guardrail sensitive-info filter (this is the one the guardrail exists to stop)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;D&lt;/strong&gt;enial of service&lt;/td&gt;
&lt;td&gt;Velocity spikes consistent with card-testing rather than genuine usage&lt;/td&gt;
&lt;td&gt;Rate-limiting at API Gateway + card-testing runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;E&lt;/strong&gt;levation of privilege&lt;/td&gt;
&lt;td&gt;Any request pattern that resembles probing the core-banking proxy for scopes beyond transaction read&lt;/td&gt;
&lt;td&gt;IAM least privilege + CloudWatch anomaly alarms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The knowledge base holds one runbook section per row, which is why the chunking decision in step 3 mattered as much as it did — a chunk that blends "spoofing" and "repudiation" guidance produces exactly the kind of blended, half-right answer that erodes an analyst's trust in the tool after two or three uses.&lt;/p&gt;




&lt;h2&gt;
  
  
  15 What broke, and what I'd do differently
&lt;/h2&gt;

&lt;p&gt;A few honest notes, since most write-ups skip this part:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first version of the instructions let the agent answer from memory when knowledge base retrieval came back thin.&lt;/strong&gt; Turning on contextual grounding in the guardrail (step 8) fixed this more reliably than any amount of prompt tuning — treat grounding as a guardrail-layer control, not a prompt-layer one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chunk size was the single highest-leverage tuning knob&lt;/strong&gt;, above model choice, above temperature, above instruction wording. If your retrieval quality is inconsistent, look at chunking (step 3) before you look at the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM scoping (step 4) took longer than every other piece of this build combined.&lt;/strong&gt; Getting the security team comfortable with "an LLM agent has a role that can invoke this Lambda" required the permissions boundary, a CloudTrail-backed review process, and a tabletop walkthrough of what happens if the model is prompt-injected through a transaction memo field. Budget for that conversation; it's not a formality in a cards environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'd add a human-in-the-loop confirmation step before &lt;code&gt;sns-notify-fn&lt;/code&gt; fires in production&lt;/strong&gt;, even though it's a read-only-adjacent notification. An agent that pages someone at 3 a.m. on a false positive burns trust fast.&lt;/p&gt;

&lt;p&gt;None of this is exotic. It's the same discipline any of us would apply to a human-facing fraud process — least privilege, grounded evidence, a clear audit trail — applied to an agent instead of a person. The interesting part isn't that Bedrock can do agentic orchestration; it's that the hard 80% of shipping it in a bank was identical to the hard 80% of shipping anything else in a bank.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I'd take this next
&lt;/h2&gt;

&lt;p&gt;The natural extension is multi-agent collaboration — a supervisor agent that routes between this fraud-pattern reviewer and a separate compliance-documentation agent for SAR (suspicious activity report) drafting, which Bedrock now supports natively. I haven't shipped that part yet, so I'll leave it for a follow-up post once it's been through a security review rather than write about it speculatively here.&lt;/p&gt;

&lt;p&gt;If you're building something similar on a legacy cards or core-banking stack, I'd genuinely like to compare notes — particularly on how you scoped IAM for action groups touching a system of record, and how your security team handled the guardrail sign-off. Drop a comment or find me on LinkedIn.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>aws</category>
      <category>security</category>
    </item>
  </channel>
</rss>
