<?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>Forem</title>
    <description>The most recent home feed on Forem.</description>
    <link>https://forem.com</link>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed"/>
    <language>en</language>
    <item>
      <title>What brings you by a conference booth?</title>
      <dc:creator>Amara Graham</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:39:18 +0000</pubDate>
      <link>https://forem.com/missamarakay/what-brings-you-by-a-conference-booth-43e3</link>
      <guid>https://forem.com/missamarakay/what-brings-you-by-a-conference-booth-43e3</guid>
      <description>&lt;p&gt;I'm thinking about in-person events more and more these days, so I'm curious - what brings you by a conference booth? &lt;/p&gt;

&lt;p&gt;What's a good experience or design you've seen? Or when do you avoid a booth (or booths!) entirely?&lt;/p&gt;

&lt;p&gt;LMK 😊&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>How AI Agent Payments Actually Work — And Where They Break</title>
      <dc:creator>Maxim Berg</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:38:36 +0000</pubDate>
      <link>https://forem.com/maxberg/how-ai-agent-payments-actually-work-and-where-they-break-183m</link>
      <guid>https://forem.com/maxberg/how-ai-agent-payments-actually-work-and-where-they-break-183m</guid>
      <description>&lt;p&gt;OpenAI spent months building Instant Checkout — "Buy it in ChatGPT" with Stripe, Etsy, a million Shopify merchants. By March 2026, &lt;a href="https://www.cnbc.com/2026/03/24/openai-revamps-shopping-experience-in-chatgpt-after-instant-checkout.html" rel="noopener noreferrer"&gt;they pivoted away&lt;/a&gt;. Couldn't onboard merchants, couldn't show accurate product data, couldn't handle multi-item carts. They retreated to dedicated retailer apps that redirect users to merchant websites for the actual purchase.&lt;/p&gt;

&lt;p&gt;Two weeks later, &lt;a href="https://fortune.com/2026/04/08/agent-hallucinations-protocol-money-financial-system-economy/" rel="noopener noreferrer"&gt;Fortune asked&lt;/a&gt;: "What do you do when your AI agent hallucinates with your money?"&lt;/p&gt;

&lt;p&gt;Nobody has a good answer yet. Here's the map of why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payment stack as it exists today
&lt;/h2&gt;

&lt;p&gt;In the last 12 months, every major player shipped something. Here's what exists:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment rails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; — &lt;a href="https://stripe.com/blog/agentic-commerce-suite" rel="noopener noreferrer"&gt;Agentic Commerce Suite&lt;/a&gt; (Dec 2025). Shared Payment Tokens: scoped, time-limited, revocable credentials for agent transactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visa&lt;/strong&gt; — &lt;a href="https://usa.visa.com/about-visa/newsroom/press-releases.releaseId.22276.html" rel="noopener noreferrer"&gt;Intelligent Commerce Connect&lt;/a&gt; (Apr 2026). Single API for agent purchases, tokenization, spend controls. 30+ sandbox partners&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastercard&lt;/strong&gt; — &lt;a href="https://www.mastercard.com/us/en/business/artificial-intelligence/mastercard-agent-pay.html" rel="noopener noreferrer"&gt;Agent Pay&lt;/a&gt; with Agentic Tokens. First live transaction Sep 2025, all U.S. cardholders enabled by Nov&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PayPal&lt;/strong&gt; — &lt;a href="https://newsroom.paypal-corp.com/2025-10-28-PayPal-Launches-Agentic-Commerce-Services-to-Power-AI-Driven-Shopping" rel="noopener noreferrer"&gt;Agent Ready&lt;/a&gt; (Oct 2025). Agentic payments for existing merchants with built-in fraud detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;x402&lt;/strong&gt; — Coinbase's open protocol for stablecoin micropayments via HTTP 402. ~97M payments on Base. The &lt;a href="https://www.linuxfoundation.org/press/linux-foundation-is-launching-the-x402-foundation-and-welcoming-the-contribution-of-the-x402-protocol" rel="noopener noreferrer"&gt;x402 Foundation&lt;/a&gt; launched Apr 2026 under Linux Foundation — 22 founding members including Coinbase, Stripe, Microsoft, Google, AWS, Visa, Mastercard, American Express, Shopify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Communication protocols:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; — &lt;a href="https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation" rel="noopener noreferrer"&gt;donated to Linux Foundation&lt;/a&gt; (Dec 2025). 97M monthly SDK downloads, 10,000+ servers. Payment MCP servers from Stripe, PayPal, Worldpay, Pagos, Fipto&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2A&lt;/strong&gt; — Google's agent-to-agent protocol. 22K GitHub stars, 150+ organizations, deployed in Azure AI Foundry and Amazon Bedrock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agent frameworks:&lt;/strong&gt; LangChain, CrewAI, AutoGen, OpenAI Agents SDK, Claude tool use, Gemini agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every layer is covered except one.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of an agent payment
&lt;/h2&gt;

&lt;p&gt;When an AI agent spends money, here's what actually happens — step by step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Intent       → Agent decides it needs something
2. Discovery    → Agent finds the tool/API/merchant
3. Selection    → Agent picks what to buy and from whom
4. ???????????? → ????????????????????????????????????
5. Payment      → Money moves
6. Confirmation → Receipt, audit log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 is the problem.&lt;/p&gt;

&lt;p&gt;Between "I want to buy this" and "money sent" — there is no standard layer that asks: &lt;strong&gt;should this agent spend this amount on this thing right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What "no standard layer" means, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frameworks have monitoring, not enforcement.&lt;/strong&gt; CrewAI has iteration caps. LangChain has observability hooks. Post-hoc cost tracking exists. Pre-execution enforcement of dollar-denominated policies does not. No framework understands "$50 on food" vs "$50 on compute."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payment processors handle fraud, not policy.&lt;/strong&gt; "Your agent shouldn't spend more than $200/day on SaaS" isn't fraud — it's governance. Different problem, different layer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LLM providers offer org-level caps, not per-agent controls.&lt;/strong&gt; Your agent blowing $500 on a single API call looks identical to 500 legitimate $1 calls.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So companies reinvent Step 4 every time. Hardcoded limits. Slack approval bots. "Please don't spend too much" in the system prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where policies can't live
&lt;/h2&gt;

&lt;p&gt;If you accept that governance belongs at Step 4, the next question is: &lt;strong&gt;who runs it?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Not in the prompt
&lt;/h3&gt;

&lt;p&gt;"Please limit spending to $100 per day" in a system prompt is not a spending control. It's a suggestion.&lt;/p&gt;

&lt;p&gt;LLMs hallucinate. They reinterpret instructions. They prioritize task completion over constraints. And with prompt injection, an attacker can override your rules entirely. Security researchers have &lt;a href="https://stellarcyber.ai/learn/agentic-ai-securiry-threats/" rel="noopener noreferrer"&gt;documented patterns&lt;/a&gt; of gradual prompt-based escalation: agents manipulated through "clarification" messages over days or weeks, each interaction nudging the spending authorization boundary until the agent operates well beyond its original constraints.&lt;/p&gt;

&lt;p&gt;That's not a guardrail. That's a prayer.&lt;/p&gt;

&lt;p&gt;And the tooling layer itself is under pressure. In April 2026, OX Security disclosed &lt;a href="https://www.theregister.com/2026/04/15/mcp_supply_chain/" rel="noopener noreferrer"&gt;RCE vulnerabilities in MCP implementations&lt;/a&gt; — the same protocol that Stripe, PayPal, and Worldpay use for agent payments. Anthropic disputes the severity. But both sides agree that tool-level security depends on the user correctly evaluating each action. A compromised MCP server can alter transaction amounts and redirect payments. Prompt-based spending controls and tool-level trust are separate problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not in the payment processor
&lt;/h3&gt;

&lt;p&gt;Stripe, Visa, and Mastercard are building excellent infrastructure. But it operates at the &lt;strong&gt;transaction level&lt;/strong&gt;, not the &lt;strong&gt;intent level&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A processor sees: "charge $47.99, category: food_delivery." It doesn't see: "this agent has a $15/person lunch budget and already spent $120 today." Hard limits on the card can't enforce contextual business rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not in the agent framework
&lt;/h3&gt;

&lt;p&gt;LangChain and CrewAI control tool execution. They can intercept a function call, log it, even block it. But they don't understand financial semantics. "$50 on food" and "$50 on cloud compute" trigger the same callback. The framework doesn't know your daily food budget is $30 and your compute budget is $500.&lt;/p&gt;

&lt;p&gt;You could build this logic inside the framework. People do. That's the "writing authentication from scratch before OAuth" problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where they belong: a dedicated middleware layer
&lt;/h3&gt;

&lt;p&gt;The pattern that works is a &lt;strong&gt;separate policy layer&lt;/strong&gt; between intent and execution.&lt;/p&gt;

&lt;p&gt;The agent says "I want to spend X on Y." The policy layer checks rules deterministically — not with an LLM, with code — and returns approve, deny, or escalate. Then (and only then) the payment happens.&lt;/p&gt;

&lt;p&gt;This is the same architectural pattern as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OAuth&lt;/strong&gt; — doesn't live in the browser or the database. Separate auth layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPA&lt;/strong&gt; — doesn't live in the app or the infrastructure. Separate policy engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewalls&lt;/strong&gt; — don't live in the OS kernel or the application. Separate network layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent spending governance is infrastructure, not application logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What governance actually checks
&lt;/h2&gt;

&lt;p&gt;A policy engine for agent spending evaluates requests against declarative rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is this agent active?&lt;/td&gt;
&lt;td&gt;Disabled agents can't spend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is this category allowed?&lt;/td&gt;
&lt;td&gt;"gambling" → denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per-request limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is this single purchase too large?&lt;/td&gt;
&lt;td&gt;$500 request, $200 limit → denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schedule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is spending allowed right now?&lt;/td&gt;
&lt;td&gt;Procurement agent outside business hours → denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Daily limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Has the agent hit today's cap?&lt;/td&gt;
&lt;td&gt;$450 spent today, $500 limit, requesting $100 → denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weekly limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;This week's cap?&lt;/td&gt;
&lt;td&gt;Same logic, wider window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;This month's cap?&lt;/td&gt;
&lt;td&gt;Same logic, wider window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total budget&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lifetime budget remaining?&lt;/td&gt;
&lt;td&gt;$4,800 of $5,000 spent, requesting $300 → denied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every check is deterministic. No LLM in the loop. The agent gets back a structured response — approved with budget remaining, or denied with a specific reason. A well-behaved agent adjusts. The enforcement must be deterministic; an LLM can translate human intent into policy JSON, but it shouldn't be in the enforcement loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two types of agent spending
&lt;/h2&gt;

&lt;p&gt;A distinction most articles miss. There are two fundamentally different kinds of agent purchases, and they need different payment rails but the &lt;strong&gt;same governance layer&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machine-consumable resources&lt;/strong&gt; — APIs, compute, data, cloud services. High frequency, small amounts, no physical delivery. This is where x402 shines: agent hits an API, gets a 402 response with payment instructions, pays in USDC on Base, retries with proof. Sub-second. Sub-cent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human-consumable goods&lt;/strong&gt; — food delivery, SaaS subscriptions, physical products. Lower frequency, larger amounts, complex fulfillment. Stripe, Visa, Mastercard territory.&lt;/p&gt;

&lt;p&gt;An agent ordering compute for $0.003 and ordering lunch for $15 need completely different payment rails. But the question "should this agent spend this amount right now?" is identical. A unified policy layer tracks spending across both rails in USD-equivalent and maintains one audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The liability question
&lt;/h2&gt;

&lt;p&gt;If an agent spends $12,000 instead of $500, who pays? The platform? The user who set the rules? The card issuer? The merchant?&lt;/p&gt;

&lt;p&gt;EU's PSD2 requires "strong customer authentication" — a framework that doesn't account for non-human actors. An agent can't do biometric verification. It can't confirm intent through a second device. Regulatory frameworks assume a human in the loop, and agents break that assumption.&lt;/p&gt;

&lt;p&gt;This is why compliance teams will require governance layers before agents get payment access. Without an auditable, deterministic policy check between intent and payment, there's no answer to "who approved this?" that satisfies a regulator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Short term (2026):&lt;/strong&gt; Basic policy engines. Per-agent budgets, category restrictions, time limits, approval thresholds. Companies will require this the way they require SSO — because compliance demands it. FINRA already &lt;a href="https://www.finra.org/rules-guidance/guidance/reports/2026-finra-annual-regulatory-oversight-report/gen-ai" rel="noopener noreferrer"&gt;flagged&lt;/a&gt; agents "acting beyond the user's actual or intended scope and authority."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medium term (2027):&lt;/strong&gt; Contextual policies. "Max $200/request for compute, $50 for food, unlimited for pre-approved vendors." Corporate purchasing has done this for humans for decades, but agents operate at machine speed across dozens of tools, generating hundreds of transactions per hour. An agent can't be pulled into a meeting to justify a purchase. The governance layer encodes business context upfront. Multi-agent governance follows: agent A delegates budget to agent B with scoped authority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long term (2028+):&lt;/strong&gt; Adaptive policies. Anomaly detection for waste, not just fraud. Cross-org benchmarks: "agents in your industry typically spend $X on Y."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fortune.com/2026/04/14/nava-seed-funding-ai-financial-agents/" rel="noopener noreferrer"&gt;Nava&lt;/a&gt; just raised $8.3M to build escrow for agent transactions. &lt;a href="https://solvapay.com" rel="noopener noreferrer"&gt;SolvaPay&lt;/a&gt; raised €2.4M for agentic payment infrastructure. Two funded startups in one week, both solving variations of the same problem. Market forecasts range from $547M (Sanbi.ai, 2033) to $1.5T (Juniper Research, 2030). The real number depends on trust. And trust requires governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The firewall moment
&lt;/h2&gt;

&lt;p&gt;We've been here before. Authentication before OAuth. Authorization before OPA. Network security before firewalls. Every time: "each team builds their own" → "there's a standard layer for this."&lt;/p&gt;

&lt;p&gt;Agent spending governance is at the "each team builds their own" stage. Vendor surveys say &lt;a href="https://www.helpnetsecurity.com/2026/03/03/enterprise-ai-agent-security-2026/" rel="noopener noreferrer"&gt;80% of organizations report risky agent behaviors&lt;/a&gt;. Take that with a grain of salt. But the direction is clear, and the payment stack is making it easier to spend every month.&lt;/p&gt;

&lt;p&gt;The capability layer is built. The governance layer is next. Standards bodies are working on it. The question is whether it'll happen before or after the first headline-making incident.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm building an open-source approach to this at &lt;a href="https://letagentpay.com" rel="noopener noreferrer"&gt;LetAgentPay&lt;/a&gt; — policy engine with Python/TypeScript SDKs and an MCP server — so I'm not a neutral observer. But the architectural pattern described here matters more than any single implementation. If you're building agents that spend money, I'd genuinely love to hear how you're handling governance today.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Database Subsetting for PostgreSQL: A Practical Guide (2026)</title>
      <dc:creator>Jake Lazarus</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:35:00 +0000</pubDate>
      <link>https://forem.com/jakelaz/database-subsetting-for-postgresql-a-practical-guide-2026-4j5m</link>
      <guid>https://forem.com/jakelaz/database-subsetting-for-postgresql-a-practical-guide-2026-4j5m</guid>
      <description>&lt;p&gt;Every team that has tried to copy production data into a dev environment has hit the same wall: production is too big, full of PII, and growing. The fix is not a bigger laptop or a faster &lt;code&gt;pg_dump&lt;/code&gt;. It is &lt;strong&gt;database subsetting&lt;/strong&gt; — extracting a small, self-contained slice of the database instead of all of it.&lt;/p&gt;

&lt;p&gt;Subsetting is the workflow underneath almost every modern dev-data tool. It is what makes "restore production data locally" actually viable. But the term gets thrown around loosely, and the difference between a real FK-aware subset and a glorified &lt;code&gt;SELECT ... LIMIT&lt;/code&gt; is the difference between a working dev environment and a database full of orphaned rows.&lt;/p&gt;

&lt;p&gt;This guide is the canonical version. What subsetting is, how it works at the foreign-key level, the strategies that work for common PostgreSQL schemas, and a 2026 honest look at the tools that do it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Database subsetting extracts a referentially complete slice of a PostgreSQL database by traversing foreign keys from one or more root tables. Done well, it produces a dataset 10–1000× smaller than production that still behaves like production. Done badly, it produces broken referential integrity and silent test failures. This post covers how to do it well, the strategies that fit common schemas, and the tools that handle it in 2026.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is database subsetting?
&lt;/h2&gt;

&lt;p&gt;Database subsetting is the process of extracting a representative, self-contained slice of a database instead of copying the whole thing. You start from one or more &lt;strong&gt;root tables&lt;/strong&gt; — usually entities like &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;accounts&lt;/code&gt;, or &lt;code&gt;tenants&lt;/code&gt; — apply filters and row limits, and then traverse foreign key relationships to pull in every related row that the slice depends on.&lt;/p&gt;

&lt;p&gt;The output is a smaller database that preserves the same schema and the same relational structure as production. Every foreign key still resolves. Every join still returns rows. The dataset behaves like production, just at 1% (or 0.1%, or 0.01%) of the size.&lt;/p&gt;

&lt;p&gt;The point of subsetting is &lt;strong&gt;not&lt;/strong&gt; to make a backup. It is to produce a dataset small enough to be useful for development, CI, and staging while still being realistic enough to surface the bugs that fake data hides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why teams need database subsetting
&lt;/h2&gt;

&lt;p&gt;The motivation is almost always the same: &lt;strong&gt;&lt;code&gt;pg_dump&lt;/code&gt; does not scale, and seed scripts do not survive contact with reality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A full &lt;code&gt;pg_dump&lt;/code&gt; of a mature production database is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too big.&lt;/strong&gt; A 200GB database is fine on production hardware. It is unusable on a laptop, painful in CI, and a pain to refresh weekly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full of PII.&lt;/strong&gt; Real emails, real names, real billing addresses end up on dev machines, in CI logs, and in artifacts. That is a compliance problem in any environment that handles customer data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow to restore.&lt;/strong&gt; A restore that takes 90 seconds today takes 8 minutes in 18 months as production grows. That cost compounds across every CI run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We covered the full breakdown in &lt;a href="https://basecut.dev/vs/pg-dump" rel="noopener noreferrer"&gt;pg_dump vs database snapshots&lt;/a&gt; — the headline is that &lt;code&gt;pg_dump&lt;/code&gt; is the right tool for backups and disaster recovery, and the wrong tool for dev data.&lt;/p&gt;

&lt;p&gt;Seed scripts have the opposite problem. They start small and stay small, so size is not an issue. But every schema migration is a chance for the seed script to break, drift, or silently produce stale data. The shapes of real data — Unicode, NULLs, accounts with hundreds of related rows — never appear in hand-written fixtures. We covered why that matters in &lt;a href="https://basecut.dev/blog/why-fake-postgresql-test-data-misses-bugs" rel="noopener noreferrer"&gt;Why Fake PostgreSQL Test Data Misses Real Bugs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Subsetting solves both problems at once. The output is small enough to be useful and real enough to be representative.&lt;/p&gt;

&lt;h2&gt;
  
  
  How FK-aware subsetting actually works
&lt;/h2&gt;

&lt;p&gt;Most of the difference between "good subsetting" and "bad subsetting" comes down to whether the extractor understands foreign keys. Here is what FK-aware extraction is doing under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root tables and traversal
&lt;/h3&gt;

&lt;p&gt;You pick one or more root tables. These are the entities the subset is "about" — usually &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;accounts&lt;/code&gt;, &lt;code&gt;tenants&lt;/code&gt;, or &lt;code&gt;organizations&lt;/code&gt;. The extractor reads the rows that match your filter from the root tables and then walks the foreign key graph to pull in everything that depends on those rows.&lt;/p&gt;

&lt;p&gt;If your root is &lt;code&gt;users&lt;/code&gt; and you select 1000 rows, the extractor follows every foreign key that points at &lt;code&gt;users&lt;/code&gt; and pulls in the matching rows from &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;subscriptions&lt;/code&gt;, &lt;code&gt;audit_logs&lt;/code&gt;, and any other dependent table. It then walks one level deeper: &lt;code&gt;orders&lt;/code&gt; has FKs to &lt;code&gt;line_items&lt;/code&gt; and &lt;code&gt;payments&lt;/code&gt;, so those come along. And so on, until the closure is complete.&lt;/p&gt;

&lt;p&gt;This is the part that matters. Without traversal, you end up with 1000 users and zero orders, because nothing told the extractor to follow &lt;code&gt;orders.user_id&lt;/code&gt;. With traversal, you get a connected subgraph: 1000 users, all of their orders, all of those orders' line items, all of the related payments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filters and row limits
&lt;/h3&gt;

&lt;p&gt;Filters narrow the slice before traversal runs. They look like SQL &lt;code&gt;WHERE&lt;/code&gt; clauses applied to the root tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:since&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:plan'&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;since&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-01-01'&lt;/span&gt;
      &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Row limits cap the slice in case the filter still pulls in too much. A &lt;code&gt;per_table&lt;/code&gt; limit prevents one accidentally-huge table from blowing up the snapshot, and a &lt;code&gt;total&lt;/code&gt; limit caps the whole extract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;per_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filters and limits together are the levers that make subsetting work for any size of production database. The same config that works on 10GB of data works on 10TB — only the filter changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why referential completeness matters
&lt;/h3&gt;

&lt;p&gt;A subset is &lt;strong&gt;referentially complete&lt;/strong&gt; when every foreign key in the extract resolves to a row that is also in the extract. If &lt;code&gt;orders.user_id = 42&lt;/code&gt; is in the extract but &lt;code&gt;users.id = 42&lt;/code&gt; is not, the subset is broken and the restore will fail with a constraint violation — or worse, succeed with constraints disabled and produce a database your app cannot read correctly.&lt;/p&gt;

&lt;p&gt;This is the failure mode of "naive" subsetting (run &lt;code&gt;SELECT * FROM users LIMIT 1000&lt;/code&gt; and call it done). The extracted users table has 1000 rows. The orders table has rows pointing at user IDs that no longer exist. The restore either errors out or silently corrupts the dataset.&lt;/p&gt;

&lt;p&gt;A real FK-aware extractor guarantees referential completeness by construction: every row in the output is reachable from a root row by following foreign keys, and every foreign key in every row resolves inside the output. There are no orphans.&lt;/p&gt;

&lt;p&gt;This is the property that makes subsetting actually useful. Without it, you do not have a working database — you have a pile of disconnected rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subsetting and anonymization
&lt;/h2&gt;

&lt;p&gt;A referentially complete subset still contains real PII unless something explicitly removes it. The two operations — subsetting and anonymization — are usually run together at extraction time, before the data ever leaves production.&lt;/p&gt;

&lt;p&gt;The reason to anonymize &lt;strong&gt;during&lt;/strong&gt; extraction (not after restore) is that any post-restore approach lets real PII travel through your pipeline before the masking script runs. Real emails appear in restore logs. Real names sit on disk for the few seconds it takes the script to start. New columns added since the script was last updated never get masked at all.&lt;/p&gt;

&lt;p&gt;The fix is to anonymize as part of the extract step, deterministically, so the same source value maps to the same fake value across every related table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;anonymize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.email'&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deterministic_email&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users.full_name'&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deterministic_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deterministic masking matters because joins still need to work. If &lt;code&gt;jane@company.com&lt;/code&gt; becomes &lt;code&gt;lmitchell@example.com&lt;/code&gt; in &lt;code&gt;users&lt;/code&gt; but &lt;code&gt;kpark@example.com&lt;/code&gt; in &lt;code&gt;audit_logs&lt;/code&gt;, queries that join across tables stop returning the right rows. Determinism preserves the relationships even though every value has been replaced.&lt;/p&gt;

&lt;p&gt;We covered the full mechanics — including the difference between masking and anonymization, what GDPR considers anonymized, and why automatic detection matters — in &lt;a href="https://basecut.dev/blog/how-to-anonymize-pii-in-postgresql-for-development" rel="noopener noreferrer"&gt;How to Anonymize PII in PostgreSQL for Development&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subsetting strategies for common PostgreSQL schemas
&lt;/h2&gt;

&lt;p&gt;Most production schemas fit one of three patterns. Each one has a different "right way" to subset, and getting the strategy right matters more than which tool you use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-tenant SaaS: filter by tenant_id
&lt;/h3&gt;

&lt;p&gt;The most common shape. Every business object has a &lt;code&gt;tenant_id&lt;/code&gt; (or &lt;code&gt;org_id&lt;/code&gt;, or &lt;code&gt;account_id&lt;/code&gt;) column, and every row in the system belongs to exactly one tenant. The natural subset is "give me all the data for one tenant" or "give me all the data for the 50 most recent tenants."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenants&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:since'&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;since&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-01-01'&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extractor then follows foreign keys from &lt;code&gt;tenants&lt;/code&gt; to &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;projects&lt;/code&gt;, &lt;code&gt;documents&lt;/code&gt;, &lt;code&gt;audit_logs&lt;/code&gt;, and everything else, naturally producing a dataset that is "the last 50 tenants and everything they own." Restore time scales with tenant size, not database size.&lt;/p&gt;

&lt;p&gt;The anti-pattern here is filtering on a child table directly (&lt;code&gt;SELECT * FROM documents WHERE tenant_id IN (...)&lt;/code&gt;). You end up with documents whose owning users were not pulled in, and the joins break.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time-windowed: last N days of activity
&lt;/h3&gt;

&lt;p&gt;When you do not have a clean tenant boundary (or you want to capture cross-tenant traffic), filter by recency. Pick a root table that represents activity — &lt;code&gt;events&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;sessions&lt;/code&gt; — and grab the last 30 days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:since'&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;since&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traversal pulls in the related users, products, and line items. The result is a snapshot that captures whatever shapes of data are flowing through the system right now, including recent edge cases like new payment methods or feature flags that only enabled in the last week.&lt;/p&gt;

&lt;p&gt;This strategy is particularly good for catching regressions, because the subset always reflects the latest production patterns. Refresh weekly and you are testing against last week's data shapes, not last quarter's.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer-scoped: one specific account for repro
&lt;/h3&gt;

&lt;p&gt;When a customer reports a bug you cannot reproduce, the fastest path to a fix is usually a snapshot of just their account. Pick the customer as a root and traverse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;accounts&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:account_id'&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;account_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;acct_01HXYZ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a tiny, FK-complete database containing exactly that customer's data — anonymized so you can share it with the team, restore it locally, and reproduce the bug in seconds. This is the workflow that justifies subsetting on its own for most teams: a 30-second restore beats half a day of "can you give me your steps again?"&lt;/p&gt;

&lt;p&gt;We dig into the broader workflow in &lt;a href="https://basecut.dev/use-cases/local-development" rel="noopener noreferrer"&gt;the local development use case&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools that do PostgreSQL subsetting in 2026
&lt;/h2&gt;

&lt;p&gt;The market has shaken out a bit since 2024. Here is the honest 2026 picture.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Basecut&lt;/th&gt;
&lt;th&gt;Tonic.ai&lt;/th&gt;
&lt;th&gt;Delphix&lt;/th&gt;
&lt;th&gt;OSS Snaplet fork&lt;/th&gt;
&lt;th&gt;Hand-rolled SQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FK-aware traversal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Anonymize at extract time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (post-restore)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Referential completeness guaranteed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-detects common PII&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Config format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;YAML&lt;/td&gt;
&lt;td&gt;GUI + config&lt;/td&gt;
&lt;td&gt;GUI + agents&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;SQL / shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosted option&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (free tier)&lt;/td&gt;
&lt;td&gt;Self-host + hosted&lt;/td&gt;
&lt;td&gt;Enterprise self-host&lt;/td&gt;
&lt;td&gt;Self-host only&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Actively maintained&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No active upstream&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Teams wanting CLI + YAML, free tier&lt;/td&gt;
&lt;td&gt;Enterprise procurement&lt;/td&gt;
&lt;td&gt;Large enterprise&lt;/td&gt;
&lt;td&gt;Self-hosters with bandwidth&lt;/td&gt;
&lt;td&gt;Tiny schemas, stopgaps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few notes that the table cannot capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basecut&lt;/strong&gt; is the actively maintained CLI-first option. YAML config, FK-aware traversal, deterministic masking, and a free tier that covers small teams. Built for the same workflow Snaplet pioneered. We cover the details on &lt;a href="https://basecut.dev/blog/snaplet-alternative" rel="noopener noreferrer"&gt;the Snaplet alternative page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tonic.ai&lt;/strong&gt; is the heavyweight commercial option. Strong for enterprise procurement and SOC 2 paperwork, heavier than most teams want for "just give me dev data." &lt;a href="https://basecut.dev/vs/tonic" rel="noopener noreferrer"&gt;Full Tonic comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delphix&lt;/strong&gt; is the legacy enterprise player. Powerful, but the operational model assumes a dedicated platform team. &lt;a href="https://basecut.dev/vs/delphix" rel="noopener noreferrer"&gt;Full Delphix comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The open-source Snaplet fork&lt;/strong&gt; is on GitHub and viable if you have engineering bandwidth to self-host and own maintenance indefinitely. There is no active upstream. &lt;a href="https://basecut.dev/blog/snaplet-alternative" rel="noopener noreferrer"&gt;Context here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hand-rolled &lt;code&gt;pg_dump&lt;/code&gt; plus SQL scripts&lt;/strong&gt; is what most teams default to before they have evaluated anything. It works at small schemas and breaks quietly as they grow. The full breakdown is in &lt;a href="https://basecut.dev/vs/pg-dump" rel="noopener noreferrer"&gt;pg_dump vs database snapshots&lt;/a&gt;, and the broader "stop writing seed scripts" argument is in &lt;a href="https://basecut.dev/blog/replace-seed-scripts-with-production-snapshots" rel="noopener noreferrer"&gt;Replace Seed Scripts with Production Snapshots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are evaluating from scratch, the honest order of operations is: pick the simplest tool that will work for your schema today, and make sure it handles referential completeness and at-extract anonymization. Everything else is detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to subset a PostgreSQL database
&lt;/h2&gt;

&lt;p&gt;Here is the minimum viable workflow with Basecut, end to end. The same five steps apply to any FK-aware tool — the syntax is just different.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Pick your root tables
&lt;/h3&gt;

&lt;p&gt;Identify the entities your subset is "about." For multi-tenant SaaS this is usually &lt;code&gt;tenants&lt;/code&gt; or &lt;code&gt;accounts&lt;/code&gt;. For a marketplace it might be &lt;code&gt;users&lt;/code&gt; or &lt;code&gt;listings&lt;/code&gt;. For an event-driven system it might be &lt;code&gt;events&lt;/code&gt; or &lt;code&gt;orders&lt;/code&gt;. Pick the table whose rows naturally pull in everything else through foreign keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Write a config
&lt;/h3&gt;

&lt;p&gt;A Basecut config defines roots, filters, limits, and anonymization rules in YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1'&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dev-snapshot'&lt;/span&gt;

&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenants&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:since'&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;since&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-01-01'&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;

&lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;per_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;

&lt;span class="na"&gt;anonymize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mode: auto&lt;/code&gt; handles common PII columns (emails, names, phones, addresses) without explicit rules. Add explicit rules later if you have unusual fields like JSONB blobs or free-text notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create a snapshot
&lt;/h3&gt;

&lt;p&gt;Run the create command against a production read replica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;basecut snapshot create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; basecut.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRODUCTION_READ_REPLICA_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basecut traverses foreign keys from your root tables, pulls in every dependent row, anonymizes PII inline, and writes a versioned, referentially complete snapshot. Real PII never leaves production.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Restore wherever you need it
&lt;/h3&gt;

&lt;p&gt;Same snapshot, any target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Local dev&lt;/span&gt;
basecut snapshot restore dev-snapshot:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Staging&lt;/span&gt;
basecut snapshot restore dev-snapshot:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAGING_DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# CI runner&lt;/span&gt;
basecut snapshot restore dev-snapshot:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="s2"&gt;"postgresql://postgres:postgres@localhost:5432/test_db"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The restore is fast because the subset is small, and safe because the data is already anonymized. We cover the CI flavor specifically in &lt;a href="https://basecut.dev/blog/postgresql-test-database-github-actions" rel="noopener noreferrer"&gt;PostgreSQL test database in GitHub Actions&lt;/a&gt; and the staging flavor in &lt;a href="https://basecut.dev/blog/how-to-set-up-a-staging-database-from-production-postgresql" rel="noopener noreferrer"&gt;setting up a staging database&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Refresh on a schedule
&lt;/h3&gt;

&lt;p&gt;A snapshot from three months ago is only as good as the data shapes from three months ago. Schedule weekly refreshes so &lt;code&gt;:latest&lt;/code&gt; always points at fresh data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;basecut snapshot create &lt;span class="nt"&gt;--config&lt;/span&gt; basecut.yml &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRODUCTION_READ_REPLICA_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it from a cron job, a CI workflow, or — on the team plan — a Basecut agent that handles scheduling for you. Existing restore commands keep working unchanged because they reference &lt;code&gt;:latest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole loop. Most teams get a working subset config in an afternoon and roll it out across local, CI, and staging over the following sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  When subsetting is not the right answer
&lt;/h2&gt;

&lt;p&gt;Subsetting is the right default for development data, but it is not the right tool for every job. Skip it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need an exact copy of production for migration rehearsal.&lt;/strong&gt; Use &lt;code&gt;pg_dump&lt;/code&gt; or a logical replication snapshot. Subsetting is a slice, not a forensic copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your schema is genuinely tiny.&lt;/strong&gt; If you have five tables and 10MB of data, subsetting is overkill. A &lt;code&gt;pg_dump&lt;/code&gt; plus a quick masking script is fine until the schema grows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have a strict requirement for full row-level fidelity.&lt;/strong&gt; Some compliance scenarios mandate a full copy with controlled access rather than a representative subset. Subsetting is a fit for the development workflow, not for forensic or audit use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else — local dev, CI test data, staging refreshes, customer repros, and onboarding — subsetting is the workflow worth investing in. It is the difference between "we have realistic dev data" and "we have a database we can actually develop against."&lt;/p&gt;




&lt;p&gt;If you want to see whether subsetting fits your schema, the Basecut free tier covers most small teams. Install the CLI, point it at a read replica, and you can have a first FK-complete, anonymized snapshot in a few minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://basecut.dev" rel="noopener noreferrer"&gt;Try Basecut free →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or read more: &lt;a href="https://basecut.dev/vs/pg-dump" rel="noopener noreferrer"&gt;pg_dump comparison&lt;/a&gt; · &lt;a href="https://basecut.dev/blog/how-to-anonymize-pii-in-postgresql-for-development" rel="noopener noreferrer"&gt;How to anonymize PII in PostgreSQL&lt;/a&gt; · &lt;a href="https://basecut.dev/blog/replace-seed-scripts-with-production-snapshots" rel="noopener noreferrer"&gt;Replace seed scripts with snapshots&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Get AI Market Analysis On-Chain: Backtested Patterns Delivered to Your Smart Contract</title>
      <dc:creator>Pythia Oracle</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:32:39 +0000</pubDate>
      <link>https://forem.com/pythiatheoracle/how-to-get-ai-market-analysis-on-chain-backtested-patterns-delivered-to-your-smart-contract-3hll</link>
      <guid>https://forem.com/pythiatheoracle/how-to-get-ai-market-analysis-on-chain-backtested-patterns-delivered-to-your-smart-contract-3hll</guid>
      <description>&lt;p&gt;What if your smart contract could receive AI-generated market intelligence — not just a price, but a pattern analysis with confidence scores, indicator snapshots, and historical accuracy data — delivered on-chain through Chainlink?&lt;/p&gt;

&lt;p&gt;That's what Pythia Visions does. This post walks through the architecture, the on-chain interface, and a full Solidity example that reacts to AI analysis automatically.&lt;/p&gt;




&lt;h2&gt;The Problem: Smart Contracts Are Blind to Market Context&lt;/h2&gt;

&lt;p&gt;Price oracles tell your contract that BTC is $72,000. They don't tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this a capitulation sell-off or a normal dip?&lt;/li&gt;
&lt;li&gt;What do RSI, EMA, Bollinger Bands, and VWAP say right now?&lt;/li&gt;
&lt;li&gt;What happened historically when this exact pattern appeared?&lt;/li&gt;
&lt;li&gt;How confident should you be in a recovery?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For any of this, you'd need to run your own off-chain pipeline — data sources, indicator computation, pattern detection, historical backtesting. Most builders don't have the time or infrastructure for that.&lt;/p&gt;

&lt;p&gt;Pythia Visions solves this by running the full analysis pipeline off-chain and delivering structured, AI-calibrated results on-chain via Chainlink.&lt;/p&gt;




&lt;h2&gt;What a Vision Contains&lt;/h2&gt;

&lt;p&gt;A Vision is a structured payload fired on-chain when a historically significant pattern is detected. Here's what arrives in the &lt;code&gt;VisionFired&lt;/code&gt; event:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tokenId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bytes32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;keccak256("BTC")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patternType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0x11&lt;/code&gt; (CAPITULATION_STRONG)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;confidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;86&lt;/code&gt; (AI-calibrated, 55-89 range)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;direction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; (BULLISH)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BTC price at detection (18 decimals)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ABI-encoded: indicators, analysis, feeds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;payload&lt;/code&gt; bytes decode to a full analysis package:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Indicator snapshot&lt;/strong&gt; — RSI, EMA gap, ATR, Bollinger position, VWAP distance, rate of change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern metadata&lt;/strong&gt; — historical accuracy, average return, sample size, data span&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI analysis&lt;/strong&gt; — 2-3 sentence explanation of what's happening&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feeds to watch&lt;/strong&gt; — which Pythia Feeds to monitor for confirmation (with thresholds)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;The Patterns (Backtested 2017-2026)&lt;/h2&gt;

&lt;p&gt;Six patterns validated against years of BTC history, covering four categories:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CAPITULATION_STRONG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Severe sell-off with multiple confirming indicators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CAPITULATION_BOUNCE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sell-off with early reversal signals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EMA_DIVERGENCE_STRONG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strong trend divergence with momentum confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EMA_DIVERGENCE_SNAP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trend divergence approaching snap-back threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BOLLINGER_EXTREME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Price at extreme statistical deviation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OVERBOUGHT_CONTINUATION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strong momentum with continuation bias&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each Vision carries its confidence score and the historical accuracy range for the detected pattern — your contract gets the full context to decide how to act.&lt;/p&gt;

&lt;p&gt;Browse available Visions at &lt;a href="https://pythia.c3x-solutions.com/visions/#available" rel="noopener noreferrer"&gt;pythia.c3x-solutions.com/visions&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;The On-Chain Interface&lt;/h2&gt;

&lt;h3&gt;PythiaVisionRegistry&lt;/h3&gt;

&lt;p&gt;Deployed on Polygon mainnet at &lt;a href="https://polygonscan.com/address/0x39407eEc3Ba80746BC6156eD924D16C2689533Ed#code" rel="noopener noreferrer"&gt;&lt;code&gt;0x39407eEc3Ba80746BC6156eD924D16C2689533Ed&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;interface IPythiaVisionRegistry {
    /// @notice Emitted when AI detects a backtested pattern
    event VisionFired(
        bytes32 indexed tokenId,
        uint8   patternType,   // 0x11 = CAPITULATION_STRONG, etc.
        uint8   confidence,    // AI-calibrated, 55-89
        uint8   direction,     // 1 = BULLISH
        uint256 price,         // 18 decimals
        bytes   payload        // ABI-encoded full analysis
    );

    /// @notice Subscribe to Visions for a token. Free — no LINK.
    function subscribe(bytes32 tokenId) external;

    /// @notice Unsubscribe
    function unsubscribe(bytes32 tokenId) external;

    /// @notice Check subscription status
    function isSubscribed(address subscriber, bytes32 tokenId)
        external view returns (bool);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Visions are free.&lt;/strong&gt; &lt;code&gt;subscribe(keccak256("BTC"))&lt;/code&gt; — that's it. No LINK, no fees, no expiry.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;VisionFired&lt;/code&gt; events are public. Anyone can read them from the event log. Subscription is optional on-chain registration — useful for contracts that want to filter or for future automation.&lt;/p&gt;

&lt;h3&gt;Pattern Type Codes&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;// BTC pattern types
uint8 constant CAPITULATION_STRONG   = 0x11;
uint8 constant CAPITULATION_BOUNCE   = 0x10;
uint8 constant EMA_DIVERGENCE_STRONG = 0x21;
uint8 constant EMA_DIVERGENCE_SNAP   = 0x20;
uint8 constant BOLLINGER_EXTREME     = 0x30;
uint8 constant OVERBOUGHT_CONT      = 0x40;&lt;/code&gt;&lt;/pre&gt;




&lt;h2&gt;Reading Visions in Your Contract&lt;/h2&gt;

&lt;p&gt;The simplest integration: listen for &lt;code&gt;VisionFired&lt;/code&gt; events and act on the structured fields.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "./interfaces/IPythiaVisionRegistry.sol";

contract SimpleVisionReader {
    IPythiaVisionRegistry public immutable visionRegistry;
    bytes32 public constant BTC = keccak256("BTC");

    uint8   public lastPattern;
    uint8   public lastConfidence;
    uint256 public lastPrice;
    uint64  public lastTimestamp;

    event VisionReceived(uint8 pattern, uint8 confidence, uint256 price);

    constructor(address _visionRegistry) {
        visionRegistry = IPythiaVisionRegistry(_visionRegistry);
    }

    /// @notice Call once after deployment — free, no LINK needed
    function subscribe() external {
        visionRegistry.subscribe(BTC);
    }

    /// @notice Called by a relay bot when VisionFired is detected
    function onVision(
        uint8 patternType,
        uint8 confidence,
        uint256 price
    ) external {
        lastPattern    = patternType;
        lastConfidence = confidence;
        lastPrice      = price;
        lastTimestamp   = uint64(block.timestamp);
        emit VisionReceived(patternType, confidence, price);
    }

    /// @notice Other contracts read this to check for active signals
    function hasRecentVision(uint64 maxAge) external view returns (bool) {
        return lastTimestamp &amp;gt; 0 &amp;amp;&amp;amp;
               block.timestamp - lastTimestamp &amp;lt;= maxAge;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Deploy on Polygon mainnet with &lt;code&gt;_visionRegistry = 0x39407eEc3Ba80746BC6156eD924D16C2689533Ed&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;Full Example: Vision-Driven Vault Guard&lt;/h2&gt;

&lt;p&gt;The real power of Visions is the &lt;strong&gt;feeds-to-watch&lt;/strong&gt; field. Each Vision tells you which Pythia Feeds to monitor for confirmation — with specific thresholds.&lt;/p&gt;

&lt;p&gt;For example, a capitulation Vision might include feeds like &lt;code&gt;btc_RSI_1H_14&lt;/code&gt; (watch for oversold exit), &lt;code&gt;btc_VWAP_24H&lt;/code&gt; (watch for VWAP reclaim), and &lt;code&gt;btc_EMA_1H_20&lt;/code&gt; (watch for EMA reclaim) — each with a specific condition and threshold tailored to the current market state.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;VisionVaultGuard&lt;/code&gt; contract automates this loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vision fires&lt;/strong&gt; (free) — AI says "BTC capitulation detected, 86% confidence"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract auto-subscribes&lt;/strong&gt; to the recommended Pythia Events (paid LINK) — "tell me when RSI crosses above 35"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events fire&lt;/strong&gt; when thresholds are hit — confirmation arrives on-chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract transitions to CONFIRMED&lt;/strong&gt; — other contracts/bots read the state and act&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code&gt;State machine:

  IDLE → ALERT → WATCHING → CONFIRMED → IDLE
         (vision   (events     (enough        (auto-reset
          fires)    subscribed)  confirmations)  after cooldown)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here's the core of the contract (full source at &lt;a href="https://github.com/pythia-the-oracle/pythia-oracle-examples" rel="noopener noreferrer"&gt;pythia-oracle-examples&lt;/a&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;contract VisionVaultGuard is ConfirmedOwner {
    enum State { IDLE, ALERT, WATCHING, CONFIRMED }

    IPythiaEventRegistry    public eventRegistry;
    IPythiaVisionRegistry   public visionRegistry;
    bytes32 public constant BTC = keccak256("BTC");

    State public state;
    uint8 public requiredConfirmations = 1;

    struct FeedWatch {
        string  feedName;    // e.g. "btc_RSI_1H_14"
        uint8   condition;   // 0=ABOVE, 1=BELOW
        int256  threshold;   // 8 decimals
    }

    /// @notice Relay bot calls this when VisionFired event is detected
    function processVision(
        uint8 patternType,
        uint8 confidence,
        uint8 direction,
        uint256 price,
        FeedWatch[] calldata feeds,
        string[] calldata meanings
    ) external onlyOwner {
        // Store vision data, transition to ALERT
        // ...

        // Auto-subscribe to each recommended feed as a Pythia Event
        for (uint256 i = 0; i &amp;lt; feeds.length; i++) {
            uint256 cost = eventRegistry.getCost(eventDays);
            LINK.approve(address(eventRegistry), cost);
            uint256 eventId = eventRegistry.subscribe(
                feeds[i].feedName,
                eventDays,
                feeds[i].condition,
                feeds[i].threshold
            );
            // Track subscription for confirmation matching
        }

        // Transition: ALERT → WATCHING
    }

    /// @notice Bot reports when a confirmation Event fires
    function reportConfirmation(uint256 eventId, int256 value) external onlyOwner {
        // Match eventId to tracked subscription, mark as fired
        // If enough confirmations → transition to CONFIRMED
    }

    /// @notice Other contracts read this
    function isActionReady() external view returns (bool) {
        return state == State.CONFIRMED &amp;amp;&amp;amp;
               block.timestamp &amp;lt; lastVision.receivedAt + confirmedTimeout;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This creates a &lt;strong&gt;Vision → Event → Action&lt;/strong&gt; loop where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visions (free) provide the intelligence — what happened and what to watch&lt;/li&gt;
&lt;li&gt;Events (paid LINK) provide the triggers — when confirmations arrive&lt;/li&gt;
&lt;li&gt;Your contract acts only when both the pattern AND the confirmations align&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full contract with 26 passing Hardhat tests: &lt;a href="https://github.com/pythia-the-oracle/pythia-oracle-examples" rel="noopener noreferrer"&gt;&lt;code&gt;06_VisionVaultGuard.sol&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;Deploying on Polygon Mainnet&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;Pythia Vision Registry: 0x39407eEc3Ba80746BC6156eD924D16C2689533Ed
Pythia Event Registry:  0x73686087d737833C5223948a027E13B608623e21
LINK Token:             0xb0897686c545045aFc77CF20eC7A532E3120E0F1&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the VisionVaultGuard, fund the contract with LINK (for Event subscriptions). Visions themselves are free.&lt;/p&gt;




&lt;h2&gt;AI-Assisted Development&lt;/h2&gt;

&lt;p&gt;Explore Visions and Feeds programmatically:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;pip install pythia-oracle-mcp&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Works with Claude, Cursor, Windsurf, or any MCP-compatible AI tool. Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"What patterns does Pythia Visions detect for BTC?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Show me the VisionFired event interface"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"What feeds should I watch after a capitulation signal?"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or use LangChain:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;pip install langchain-pythia&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;from langchain_pythia import PythiaToolkit

toolkit = PythiaToolkit()
tools = toolkit.get_tools()
# 7 tools: feeds, tokens, events, visions info, and more&lt;/code&gt;&lt;/pre&gt;




&lt;h2&gt;Why This Architecture&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why not just put AI on-chain?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Running an LLM on-chain is impossible. But the output of an AI — a confidence score, a pattern classification, a set of indicator values — is just data. Data that Chainlink already knows how to deliver.&lt;/p&gt;

&lt;p&gt;Pythia's approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mechanical detection&lt;/strong&gt; (free, deterministic) decides IF something fires&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI calibration&lt;/strong&gt; (one cheap API call) adds nuance to HOW confident we are&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chainlink delivery&lt;/strong&gt; (trustless) gets it on-chain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI is one step in the middle — not the foundation. If AI is down, Visions still fire with mechanical defaults. The patterns are the product; AI makes them better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why backtested patterns?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every pattern in production has been validated against years of BTC history. Patterns that looked strong on shorter windows but degraded on full history were dropped. Data decides what ships.&lt;/p&gt;




&lt;h2&gt;What You Can Build With This&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Risk management layers&lt;/strong&gt; — pause lending/vault operations when high-confidence capitulation is detected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated DCA strategies&lt;/strong&gt; — increase position size when AI confidence is above 80%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert systems&lt;/strong&gt; — relay Vision data to Telegram/Discord bots for trading teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio rebalancing&lt;/strong&gt; — shift allocations based on detected market regime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeper-style bots&lt;/strong&gt; — watch for VisionFired events and execute strategies across protocols&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;Summary&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pythia Visions deliver AI-calibrated market intelligence on-chain via Chainlink&lt;/li&gt;
&lt;li&gt;6 backtested BTC patterns covering capitulations, divergences, extremes, and momentum&lt;/li&gt;
&lt;li&gt;Free subscription — &lt;code&gt;subscribe(keccak256("BTC"))&lt;/code&gt;, no LINK&lt;/li&gt;
&lt;li&gt;Structured payload: pattern type, confidence, indicators, analysis, feeds-to-watch&lt;/li&gt;
&lt;li&gt;VisionVaultGuard example: automated Vision → Event → Action loop&lt;/li&gt;
&lt;li&gt;Mainnet contract: &lt;a href="https://polygonscan.com/address/0x39407eEc3Ba80746BC6156eD924D16C2689533Ed#code" rel="noopener noreferrer"&gt;&lt;code&gt;0x39407eEc3Ba80746BC6156eD924D16C2689533Ed&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The intelligence is live. The patterns are firing. What you build on top is up to you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Explore: &lt;a href="https://pythia.c3x-solutions.com/visions/" rel="noopener noreferrer"&gt;pythia.c3x-solutions.com/visions&lt;/a&gt; | MCP: &lt;code&gt;pip install pythia-oracle-mcp&lt;/code&gt; | Examples: &lt;a href="https://github.com/pythia-the-oracle/pythia-oracle-examples" rel="noopener noreferrer"&gt;github.com/pythia-the-oracle/pythia-oracle-examples&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>Migrate your Drools rules to OrqueIO DMN</title>
      <dc:creator>Ghofrane WECHCRIA</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:32:22 +0000</pubDate>
      <link>https://forem.com/ghofrane_wechcria_50fe903/migrate-your-drools-rules-to-orqueio-dmn-1gb0</link>
      <guid>https://forem.com/ghofrane_wechcria_50fe903/migrate-your-drools-rules-to-orqueio-dmn-1gb0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a business process, decision-making is essential because it defines the logic that guides the execution of activities. Historically, this logic was externalized into rule engines such as Drools, separating decision-making from orchestration. However, in today’s environments where transparency and collaboration are key, this separation reveals its limitations: rules often remain hidden in technical files, difficult to understand and maintain.&lt;br&gt;
To address these challenges, organizations now seek more integrated approaches. This is exactly what OrqueIO DMN offers: a model in which decisions and processes are brought together within the same ecosystem, providing consistency, clarity, and traceability.&lt;/p&gt;


&lt;h2&gt;
  
  
  1.OrqueIO — The future of open-source orchestration
&lt;/h2&gt;

&lt;p&gt;Before introducing DMN, it’s important to present the execution platform.&lt;br&gt;
OrqueIO is a 100% open-source fork of Camunda, ensuring the long-term future of BPMN automation, maintaining full compatibility with existing workflows while extending and modernizing the engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What OrqueIO brings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% open-source and fully compatible with your current BPMN/DMN models, connectors, scripts, and APIs&lt;/li&gt;
&lt;li&gt;Ongoing maintenance and guaranteed security updates, ensuring long-term reliability and stability&lt;/li&gt;
&lt;li&gt;New, practical features for daily use, including improved Cockpit monitoring and native DMN integration&lt;/li&gt;
&lt;li&gt;Enhanced performance and engine stability, even under high load&lt;/li&gt;
&lt;li&gt;Expert technical support, transparent and reliable — with no vendor lock-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, OrqueIO enables organizations to preserve their existing investments while adopting a modern, sustainable, and open orchestration platform — an environment where integrated decision logic with DMN naturally thrives.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. The limitations of Drools
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Excessive separation from the BPMN process:&lt;/strong&gt;&lt;br&gt;
The rules are stored in .drl files, often isolated in a separate project or module, which scatters business logic and makes it difficult to understand the relationship between decisions and the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance complexity:&lt;/strong&gt;&lt;br&gt;
This separation between the rule engine and the process leads to difficulties in maintaining overall system consistency over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Barrier for business teams:&lt;/strong&gt;&lt;br&gt;
The rules are written in technical languages such as DRL or MVEL, requiring development skills.&lt;br&gt;
Non-technical users cannot read or modify these rules without IT involvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of traceability and transparency:&lt;/strong&gt;&lt;br&gt;
Drools does not natively provide decision traceability (which rules were applied and why).&lt;br&gt;
This makes audits and regulatory compliance much more difficult.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rigid update cycle:&lt;/strong&gt;&lt;br&gt;
Any modification to a rule requires a full redeployment of the engine or application module, slowing down update cycles and reducing responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits in agility and governance:&lt;/strong&gt;&lt;br&gt;
The Drools model does not encourage collaboration between business and IT teams, nor does it provide clear governance of decision rules integrated into BPMN processes.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. OrqueIO DMN: a unified, readable, and open-source approach
&lt;/h2&gt;

&lt;p&gt;OrqueIO natively integrates the DMN standard (Decision Model and Notation), enabling the unification of process logic (BPMN) and decision logic within the same platform.&lt;br&gt;
Whereas Drools externalizes rules into a separate rule engine, OrqueIO provides an integrated, visual, and transparent approach within a recognized open-source automation ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clear and accessible rule representation&lt;/strong&gt;&lt;br&gt;
In OrqueIO, decisions are modeled as DMN decision tables.&lt;br&gt;
Each row represents a rule, and each column represents a condition or an output.&lt;br&gt;
This visual format is understandable to both business users and developers, replacing the DRL or MVEL source code used in Drools.&lt;br&gt;
Business analysts can define, adjust, and validate decision logic without heavy reliance on technical teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native BPMN ↔ DMN Integration&lt;/strong&gt;&lt;br&gt;
In OrqueIO, a Business Rule Task can directly invoke a DMN decision table from within the same project.&lt;br&gt;
No external connectors, no complex integration: the process and the decision belong to the same executable model.&lt;br&gt;
This results in a decision logic that is coherent, centralized, and fully traceable across the entire workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in traceability and audit&lt;/strong&gt;&lt;br&gt;
Every decision execution is automatically logged.&lt;br&gt;
Teams can easily review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which rules were evaluated,&lt;/li&gt;
&lt;li&gt;the input values,&lt;/li&gt;
&lt;li&gt;and the resulting outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This greatly simplifies audits, gap analysis, regulatory compliance, and continuous improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A complete and consistent ecosystem&lt;/strong&gt;&lt;br&gt;
OrqueIO provides a unified environment combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BPMN for process orchestration,&lt;/li&gt;
&lt;li&gt;DMN for decision logic,&lt;/li&gt;
&lt;li&gt;Cockpit for monitoring and analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is centralized, extensible, interoperable, and designed to enhance collaboration between business teams and IT.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Why migrate to OrqueIO DMN?
&lt;/h2&gt;

&lt;p&gt;The transition from Drools is not just a tool replacement —&lt;br&gt;
it is the adoption of a more agile, readable, and collaborative decision model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Centralization of decisions and processes&lt;/strong&gt;&lt;br&gt;
Decisions are no longer isolated in a separate technical module.&lt;br&gt;
They coexist with business processes within the same execution environment, ensuring a unified and coherent view of the system’s behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplified maintenance&lt;/strong&gt;&lt;br&gt;
Modifying a rule no longer requires redeploying the application.&lt;br&gt;
A simple update to the DMN decision table is enough to adjust the business logic, reducing delivery cycles and improving operational responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enhanced collaboration&lt;/strong&gt;&lt;br&gt;
Business teams design and adjust rules, while developers focus on integration and robustness.&lt;br&gt;
The result: fewer misunderstandings, fewer back-and-forth steps, and greater overall efficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring and transparency&lt;/strong&gt;&lt;br&gt;
With the Cockpit interface, every executed decision is observable, explainable, and interpretable.&lt;br&gt;
This transparency simplifies optimization, control, and regulatory compliance.&lt;/p&gt;

&lt;p&gt;Below is a comparison table summarizing the main differences between Drools and OrqueIO:&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.amazonaws.com%2Fuploads%2Farticles%2Ffkv30po5dkeg8wpbieue.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.amazonaws.com%2Fuploads%2Farticles%2Ffkv30po5dkeg8wpbieue.png" alt="Comparison of Drools and OrqueIO DMN" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5.Concrete example of the transition: from Drools to OrqueIO DMN
&lt;/h2&gt;

&lt;p&gt;We want to apply discounts based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the customer type (Standard, Premium, VIP)&lt;/li&gt;
&lt;li&gt;the order amount (orderAmount)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Drools version (Before)&lt;/strong&gt;&lt;br&gt;
This &lt;em&gt;.drl&lt;/em&gt; file contains the rules, written in Drools syntax&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.orqueio.bpm.exemple.dmn.model.Order&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"Standard &amp;lt; 100"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"Standard 100..500"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"Standard &amp;gt;= 500"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"Premium &amp;lt; 100"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Premium"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"Premium 100..500"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Premium"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"Premium &amp;gt;= 500"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Premium"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"VIP &amp;lt; 500"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"VIP"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"VIP 500..1000"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"VIP"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s"&gt;"VIP &amp;gt;= 1000"&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"VIP"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implementation with DMN in OrqueIO&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With DMN, the decision logic is defined in the form of a decision model, in which we explicitly describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which data serves as inputs to the decision,&lt;/li&gt;
&lt;li&gt;which conditions are applied,&lt;/li&gt;
&lt;li&gt;and which result must be produced.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model can be edited visually, either as a table or as a diagram, which allows business teams to understand and adjust the logic more easily.&lt;/p&gt;

&lt;p&gt;At the same time, that same logic is stored in a standard DMN XML file, which is used by the engine for execution.&lt;br&gt;
The graphical view and the XML view are simply two representations of the same model, always kept in sync.&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.amazonaws.com%2Fuploads%2Farticles%2Fp459273upvpedvwpxnch.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.amazonaws.com%2Fuploads%2Farticles%2Fp459273upvpedvwpxnch.png" alt="Capture for the decision section" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the BPMN diagram, you simply add a Business Rule Task:&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.amazonaws.com%2Fuploads%2Farticles%2Furlus2qi3x5hfbzxyaah.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.amazonaws.com%2Fuploads%2Farticles%2Furlus2qi3x5hfbzxyaah.png" alt="BPMN Workflow for an Order Processing" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Calling a decision does not require any complex configuration.&lt;br&gt;
In the BPMN diagram, a simple Business Rule Task is enough.&lt;br&gt;
You only need to specify the decision reference (the ID defined in the DMN model), and the engine takes care of the evaluation.No additional technical configuration is required.&lt;br&gt;
The decision is executed automatically when the BPMN flow reaches the decision task.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Migrating from Drools to OrqueIO DMN is more than a modernization effort — it is a strategic shift toward transparency, agility, and collaboration. By bringing decision logic closer to BPMN processes, organizations eliminate silos, simplify maintenance, and empower business teams to directly contribute to the evolution of rules. The result is a unified, traceable, and adaptable system where decisions are easier to understand, update, and audit.&lt;/p&gt;

&lt;p&gt;With OrqueIO DMN, your business logic becomes clearer, your delivery cycles faster, and your governance stronger. This transition enables organizations to move from rigid rule engines to an integrated decision model that supports continuous improvement and operational efficiency.&lt;/p&gt;




&lt;p&gt;🌐 Learn more about &lt;a href="https://www.orqueio.io/" rel="noopener noreferrer"&gt;OrqueIO&lt;/a&gt;&lt;br&gt;
📂 Explore the &lt;a href="https://github.com/OrqueIO/example-order-process" rel="noopener noreferrer"&gt;example Order Process&lt;/a&gt; from the OrqueIO webinar&lt;/p&gt;

</description>
      <category>drools</category>
      <category>orqueio</category>
      <category>dmn</category>
      <category>businessprocesses</category>
    </item>
    <item>
      <title>How to Separate Cron Success and Failure in Your Daily Logs</title>
      <dc:creator>anicca</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:31:36 +0000</pubDate>
      <link>https://forem.com/anicca_301094325e/how-to-separate-cron-success-and-failure-in-your-daily-logs-m44</link>
      <guid>https://forem.com/anicca_301094325e/how-to-separate-cron-success-and-failure-in-your-daily-logs-m44</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Cron work is easier to debug when you separate execution success from delivery failure, discovery failure, and configuration failure. That was the main signal in today's diary.&lt;/p&gt;

&lt;p&gt;This article shows a simple way to keep daily logs in those four buckets so you can recover faster later.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A daily diary or ops log&lt;/li&gt;
&lt;li&gt;Cron jobs that produce artifacts or traces&lt;/li&gt;
&lt;li&gt;A habit of not collapsing every failure into one vague note&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Log execution success by itself
&lt;/h2&gt;

&lt;p&gt;Start with the jobs that actually completed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app-metrics succeeded
mau-tiktok hook fetch, trim, and stitch succeeded
reelclaw widget demo generation and direct post succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this section short and factual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Log delivery failure separately
&lt;/h2&gt;

&lt;p&gt;A job can succeed internally and still fail at the delivery layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Postiz DNS failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells you the work was produced, but the handoff broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Log discovery failure separately
&lt;/h2&gt;

&lt;p&gt;Search and existence checks are a different class of problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rg unavailable
missing SKILL.md
missing directory reference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These failures happen before the actual job logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Log configuration failure separately
&lt;/h2&gt;

&lt;p&gt;Broken paths and wrong references deserve their own bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;missing SKILL.md
missing directory reference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes it obvious that the issue is wiring, not content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lesson&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Do not mix categories&lt;/td&gt;
&lt;td&gt;Execution success, delivery failure, discovery failure, and configuration failure should stay separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write only facts&lt;/td&gt;
&lt;td&gt;Keep the log grounded in what you actually saw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faster follow-up&lt;/td&gt;
&lt;td&gt;Clear buckets make the next investigation much faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>devops</category>
      <category>observability</category>
      <category>cron</category>
      <category>logging</category>
    </item>
    <item>
      <title>What changed after our IVR started pulling data from the CRM</title>
      <dc:creator>Jack Morris</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:30:33 +0000</pubDate>
      <link>https://forem.com/jackmorris10/what-changed-after-our-ivr-started-pulling-data-from-the-crm-4hfk</link>
      <guid>https://forem.com/jackmorris10/what-changed-after-our-ivr-started-pulling-data-from-the-crm-4hfk</guid>
      <description>&lt;p&gt;Last year we rebuilt the IVR for a mid-size financial services company. Around 2,500 inbound calls a day, mix of existing customers and new leads, five departments handling everything from account inquiries to collections.&lt;/p&gt;

&lt;p&gt;The original IVR had been running for three years. It worked. Calls got answered, menus got navigated, people eventually reached a human. Nobody was complaining loudly enough for it to become a priority.&lt;/p&gt;

&lt;p&gt;Then someone pulled the actual numbers, and the picture wasn't great.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the old IVR worked
&lt;/h2&gt;

&lt;p&gt;Every caller got the same experience regardless of who they were. You'd hear a welcome message, sit through five menu options, pick one, and wait in a queue. If you picked wrong, you'd get transferred and wait again.&lt;/p&gt;

&lt;p&gt;Agents had zero context when the call connected. The first 20-30 seconds of every call was spent on "can I get your name and account number?" Even for callers who'd been customers for years. Even for someone who called yesterday about the same issue.&lt;/p&gt;

&lt;p&gt;The IVR had no idea who was calling. It couldn't. It was a standalone system with no connection to anything else in the business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's what the numbers looked like:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average handle time per call was around 4 minutes 40 seconds&lt;/li&gt;
&lt;li&gt;Roughly 35-40 seconds of that was just identification and account lookup at the start&lt;/li&gt;
&lt;li&gt;Call abandonment rate sat around 12%, mostly people dropping off during menu navigation or hold queues&lt;/li&gt;
&lt;li&gt;Overdue accounts were going through the full standard menu before reaching collections. Some of them never got there they'd pick the wrong option, land in general support, and get transferred. The transfer added another 2-3 minutes to those calls&lt;/li&gt;
&lt;li&gt;New leads from marketing campaigns were treated identically to everyone else. No priority routing, no personalized greeting, no assignment to the rep who was running the campaign&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The support team had gotten used to it. That's how phones work, right? Caller comes in, you ask who they are, you pull up the account. Standard stuff.&lt;br&gt;
We thought there was a better way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core idea&lt;/strong&gt;&lt;br&gt;
The concept was straightforward. Before the IVR plays its first word, it checks the caller's phone number against the CRM. If there's a match, the system now knows who's calling, what their account status is, who their assigned rep is, and whether they have any open tickets.&lt;/p&gt;

&lt;p&gt;That data changes everything about how the call gets handled.&lt;/p&gt;

&lt;p&gt;Instead of a one-size-fits-all menu, the IVR can make routing decisions based on actual business context. An overdue account doesn't need to hear about sales promotions. A VIP customer shouldn't wait in the general queue. A brand new lead who filled out a web form five minutes ago should hear their own name and get connected to the right rep immediately.&lt;/p&gt;

&lt;p&gt;The IVR stops being a dumb phone tree and starts acting like a front desk that actually recognizes people when they walk in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;&lt;br&gt;
We used Asterisk as the IVR platform with Kamailio handling SIP routing in front of it. The CRM was Salesforce. Between Asterisk and Salesforce, we set up a small caching service backed by Redis so the IVR wasn't hammering the Salesforce API on every single call.&lt;/p&gt;

&lt;p&gt;When a call comes in, the IVR queries the cache layer with the caller's phone number. If there's a recent record, it comes back in about 30-50 milliseconds. If not, the cache layer queries Salesforce, stores the result, and returns it. Either way, the IVR has CRM data before the caller hears anything.&lt;/p&gt;

&lt;p&gt;We normalized all phone numbers to E.164 format on both sides. This turned out to be a bigger deal than expected about 40% of initial "caller not found" results were just formatting mismatches between how Asterisk received the number and how Salesforce stored it. Same person, same number, different format. Easy fix once we found it, but it was the single biggest source of lookup failures early on.&lt;/p&gt;

&lt;p&gt;The whole lookup-to-greeting path takes under 200 milliseconds. No dead air, no awkward pause before the welcome message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The five routing paths&lt;/strong&gt;&lt;br&gt;
After the CRM lookup, every call falls into one of five buckets:&lt;br&gt;
&lt;strong&gt;Overdue accounts&lt;/strong&gt; skip the menu entirely. The system routes them straight to the collections queue. The agent's screen already shows the account details, outstanding balance, and payment history before they even pick up the call. No "can I get your account number," no transfers, no wasted time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VIP customers&lt;/strong&gt; get a personalized greeting using their name and connect directly to their assigned account manager. If that person is unavailable, they go to a priority queue with shorter wait times. They never hear the standard five-option menu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Active regular accounts&lt;/strong&gt; get the standard menu but with a difference. The agent already has their account pulled up when the call connects. That 30-40 second identification ritual at the start of every call just disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New leads&lt;/strong&gt; hear a different greeting. Something like "Hi [Name], thanks for reaching out to us." They get routed to the sales rep assigned to that lead in Salesforce. If the lead came from a specific campaign, the rep knows that too before answering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unknown callers&lt;/strong&gt; - people whose number isn't in the CRM - get the original standard menu. Nothing changes for them. The system degrades gracefully instead of breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in the numbers
&lt;/h2&gt;

&lt;p&gt;We measured everything we could over the first 90 days. Some of the improvements were expected, some caught us off guard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handle time dropped by about 18%:&lt;/strong&gt; The biggest contributor was eliminating the account identification step at the start of calls. When the agent already has the account on screen, the conversation starts with the actual issue immediately. Across 2,500 daily calls, those saved seconds add up fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call abandonment went from 12% down to around 7%:&lt;/strong&gt; Two things drove this. First, eliminating the dead air gap that happened when API lookups were slow (we solved that with the caching layer). Second, callers who got routed directly to the right place didn't have to navigate menus and wait in the wrong queue before getting transferred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collections contact rate improved noticeably:&lt;/strong&gt; Overdue accounts were actually reaching the collections team now instead of getting lost in the general menu. Before, some of those callers would pick "general inquiries," sit in a queue, explain their situation, get transferred to collections, and sit in another queue. A lot of them gave up halfway through. Direct routing removed that entire detour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New lead response time got faster:&lt;/strong&gt; Marketing was running paid campaigns that drove phone calls. Previously, those callers were treated like everyone else. Now they were recognized and connected to the right sales rep within seconds. The sales team said it made a real difference in conversion conversations when the rep could greet someone by name and reference the specific thing they'd inquired about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent satisfaction, surprisingly:&lt;/strong&gt; We didn't measure this formally, but the feedback was consistent. Agents said not having to ask "who am I speaking with" on every call made their job feel less repetitive. Having context before the conversation started let them focus on solving the problem rather than playing detective for the first minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems we didn't expect
&lt;/h2&gt;

&lt;p&gt;It wasn't all smooth. A few things caught us off guard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple accounts tied to one phone number:&lt;/strong&gt; More common than we anticipated, especially with business lines. A single number might be associated with three different accounts in Salesforce. We solved this by defaulting to the most recently active account and giving the caller a quick confirmation: "We found your account under [Company Name]. Press 1 if that's correct, press 2 to search by account number." Worked fine, but we hadn't planned for it initially.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale CRM data causing wrong routing:&lt;/strong&gt; An account marked as "overdue" in Salesforce that had actually just made a payment would still get routed to collections until the CRM record updated and the cache expired. We shortened the cache duration for accounts with recent status changes and added a webhook listener that invalidated the cache when certain Salesforce fields were modified. Took some back and forth to get right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents trusting the screen pop too much:&lt;/strong&gt; Because the system was accurate 97% of the time, agents started skipping verbal verification entirely. Usually fine, but occasionally the caller was using someone else's phone. We added a soft verification prompt to the agent script for sensitive transactions (payments, account changes) even when the screen pop was populated.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone considering this
&lt;/h2&gt;

&lt;p&gt;If your IVR handles more than a few hundred calls a day and your business logic depends on who the caller is, the CRM integration is worth doing. The impact on handle time and routing accuracy alone probably justifies the build.&lt;/p&gt;

&lt;p&gt;But don't call the CRM API directly from the IVR for every single call. It seems like the obvious approach, and it works in testing, but it won't survive production call volumes. Put a caching layer between them. You'll avoid latency spikes, rate limit issues, and token management headaches.&lt;/p&gt;

&lt;p&gt;And spend time on phone number normalization before anything else. It's not glamorous work, but mismatched number formats will quietly tank your lookup accuracy. We lost about two weeks troubleshooting "caller not found" results that turned out to be nothing more than formatting inconsistencies.&lt;/p&gt;

&lt;p&gt;The whole project from planning to production took about 6 weeks. If we did it again knowing what we know now, we could probably cut that to four.&lt;/p&gt;

&lt;p&gt;I work with the VoIP engineering team at Hire VoIP Developer we build &lt;a href="https://www.hirevoipdeveloper.com/solution/custom-ivr-solutions/" rel="noopener noreferrer"&gt;custom IVR Systems&lt;/a&gt; and telephony systems, and CRM integrations are a regular part of that work. If you've done something similar, especially with a CRM other than Salesforce, I'd be curious how you handled the data sync and caching side.&lt;/p&gt;

</description>
      <category>crm</category>
      <category>devops</category>
      <category>discuss</category>
      <category>networking</category>
    </item>
    <item>
      <title>I Built an AI News Desk for My MMA Site. Here's What Actually Worked.</title>
      <dc:creator>Paul N</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:29:01 +0000</pubDate>
      <link>https://forem.com/paulfromgidstats/i-built-an-ai-news-desk-for-my-mma-site-heres-what-actually-worked-5d94</link>
      <guid>https://forem.com/paulfromgidstats/i-built-an-ai-news-desk-for-my-mma-site-heres-what-actually-worked-5d94</guid>
      <description>&lt;p&gt;My first prompt was: "You are a sports journalist. Write a 400-word article about this UFC event." A friend texted me "bro this reads like a bot wrote it." Three published pieces in. Fair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Stack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GidStats.com runs on a custom CMS with structured fight data across UFC, PFL, LFA, DWCS. The news pipeline sits on top of that. Playwright scrapes official promotion feeds, a webhook fires a Claude API call with relevant database stats as context, output lands in staging for review before anything goes live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Prompt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need to specify what to avoid as precisely as what to produce. Mine has a 75-term banned-words list - "delve," "nuance," "tapestry" and everything else that screams AI. Explicit sentence structure instructions. Style references: ESPN MMA, The Athletic, Bloody Elbow.&lt;/p&gt;

&lt;p&gt;Structured fighter data gets injected before the generation instruction. Records, finish rates, physical attributes. The model can't hallucinate stats it's already been given, and MMA fans will fact-check you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Early on I reviewed for accuracy. Wrong question. The right question is "would I actually read this &lt;a href="https://gidstats.com/news/" rel="noopener noreferrer"&gt;MMA news&lt;/a&gt;?" I rebuilt the checklist around that: does the first sentence pull you into the second, does the fight analysis say something tactical rather than obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What It Can't Do&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Breaking news at speed - the pipeline isn't a 10-minute turnaround. Anything emotionally weighted: injuries, retirements. The model produces something flat where a human voice is the whole point.&lt;/p&gt;

&lt;p&gt;What I'd Do Differently&lt;/p&gt;

&lt;p&gt;Start with the voice problem, not the infrastructure. I spent two months on scraper architecture before solving what the output should sound like. Get one piece reading exactly right, reverse-engineer the prompt, then build the pipes. Also: inject structured data as context, always. Model memory is wrong often enough to matter.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>news</category>
    </item>
    <item>
      <title>ElastiCache Pricing Breakdown: Where the Money Actually Goes</title>
      <dc:creator>Rick Wise</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:29:01 +0000</pubDate>
      <link>https://forem.com/cloudwiseteam/elasticache-pricing-breakdown-where-the-money-actually-goes-1jc5</link>
      <guid>https://forem.com/cloudwiseteam/elasticache-pricing-breakdown-where-the-money-actually-goes-1jc5</guid>
      <description>&lt;p&gt;ElastiCache looks straightforward on the bill. You pick a node type, maybe add a replica for high availability, and move on. Then the invoice arrives and the number is bigger than the mental math suggested.&lt;/p&gt;

&lt;p&gt;The gap usually comes from one of five places: engine choice, replication topology, extended support surcharges, idle clusters, or oversized nodes nobody ever right-sized. Let's break down exactly how ElastiCache charges — and where teams get surprised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Engines, Three Price Points
&lt;/h2&gt;

&lt;p&gt;ElastiCache supports three engines: Valkey, Redis OSS, and Memcached. They don't cost the same.&lt;/p&gt;

&lt;p&gt;Valkey is &lt;strong&gt;20% cheaper&lt;/strong&gt; than Redis OSS and Memcached for node-based clusters, and &lt;strong&gt;33% cheaper&lt;/strong&gt; on ElastiCache Serverless. This isn't a promotional rate — it's the permanent pricing structure AWS launched with Valkey.&lt;/p&gt;

&lt;p&gt;For context, a cache.r7g.xlarge in us-east-1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Hourly Rate&lt;/th&gt;
&lt;th&gt;Monthly (730 hrs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valkey&lt;/td&gt;
&lt;td&gt;$0.3496&lt;/td&gt;
&lt;td&gt;~$255&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis OSS&lt;/td&gt;
&lt;td&gt;$0.437&lt;/td&gt;
&lt;td&gt;~$319&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memcached&lt;/td&gt;
&lt;td&gt;$0.437&lt;/td&gt;
&lt;td&gt;~$319&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Prices shown for us-east-1, On-Demand.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's a $64/month difference per node on a single instance type. Multiply that across a 12-node cluster and you're looking at $768/month — just from engine choice. If you're running Redis OSS and don't need Redis-specific features that Valkey doesn't support, the migration saves real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node-Based Pricing: You Pay Whether the Cache Is Hit or Not
&lt;/h2&gt;

&lt;p&gt;ElastiCache charges per node-hour from the moment a node is launched until it's terminated. Partial hours are billed as full hours. There is no scale-to-zero.&lt;/p&gt;

&lt;p&gt;A few common node types and what they cost:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node Type&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Hourly Rate&lt;/th&gt;
&lt;th&gt;Monthly (730 hrs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cache.t3.micro&lt;/td&gt;
&lt;td&gt;0.5 GiB&lt;/td&gt;
&lt;td&gt;$0.017&lt;/td&gt;
&lt;td&gt;~$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.m5.large&lt;/td&gt;
&lt;td&gt;6.38 GiB&lt;/td&gt;
&lt;td&gt;$0.156&lt;/td&gt;
&lt;td&gt;~$114&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.r7g.xlarge&lt;/td&gt;
&lt;td&gt;26.32 GiB&lt;/td&gt;
&lt;td&gt;$0.437&lt;/td&gt;
&lt;td&gt;~$319&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.r6g.16xlarge&lt;/td&gt;
&lt;td&gt;419.09 GiB&lt;/td&gt;
&lt;td&gt;$5.254&lt;/td&gt;
&lt;td&gt;~$3,835&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Prices shown for Redis OSS / Memcached in us-east-1, On-Demand. Valkey is 20% lower.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important thing to internalize: a cache.t3.micro sitting idle costs the same $12/month as one handling thousands of requests per second. The meter runs on time, not usage.&lt;/p&gt;

&lt;p&gt;AWS recommends reserving 25% of a node's memory for non-data use (replication buffers, OS overhead, etc.), so the usable capacity of a cache.r7g.xlarge is roughly 19.74 GiB, not 26.32 GiB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replication Multiplies the Bill
&lt;/h2&gt;

&lt;p&gt;Most production deployments use replication for high availability. With Redis OSS or Valkey, you configure a replication group with a primary node and one or more replica nodes per shard.&lt;/p&gt;

&lt;p&gt;Every replica is a full node charged at the same hourly rate.&lt;/p&gt;

&lt;p&gt;A three-shard cluster with one replica per shard using cache.r7g.xlarge (Valkey):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 shards × 2 nodes per shard = 6 nodes
6 × $0.3496/hr = $2.10/hr → ~$1,531/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a second replica for read scaling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 shards × 3 nodes per shard = 9 nodes
9 × $0.3496/hr = $3.15/hr → ~$2,297/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus, multi-AZ replication generates cross-AZ data transfer at $0.01/GiB in each direction. For a high-throughput cache doing 100,000 requests/second with 500-byte objects, that's roughly 167 GiB/hour of traffic. If 50% crosses AZ boundaries, that's an extra $0.84/hour — about $613/month in data transfer alone.&lt;/p&gt;

&lt;p&gt;Teams often enable multi-AZ replication on dev and staging environments where a single node would be fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless: Simpler, But Not Always Cheaper
&lt;/h2&gt;

&lt;p&gt;ElastiCache Serverless removes the node sizing decision entirely. You pay for two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data stored&lt;/strong&gt; — billed in GB-hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElastiCache Processing Units (ECPUs)&lt;/strong&gt; — a unit combining vCPU time and data transferred&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Valkey&lt;/th&gt;
&lt;th&gt;Redis OSS&lt;/th&gt;
&lt;th&gt;Memcached&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data storage&lt;/td&gt;
&lt;td&gt;$0.084/GB-hr&lt;/td&gt;
&lt;td&gt;$0.125/GB-hr&lt;/td&gt;
&lt;td&gt;$0.125/GB-hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECPUs&lt;/td&gt;
&lt;td&gt;$0.0023/M&lt;/td&gt;
&lt;td&gt;$0.0034/M&lt;/td&gt;
&lt;td&gt;$0.0034/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum data stored&lt;/td&gt;
&lt;td&gt;100 MB&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Prices shown for us-east-1.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A simple GET or SET transferring under 1 KB consumes 1 ECPU. A command transferring 3.2 KB consumes 3.2 ECPUs. Commands that use more vCPU time (like SORT or ZADD) consume proportionally more.&lt;/p&gt;

&lt;p&gt;Serverless can be cheaper for spiky workloads because you don't over-provision for peaks. But for stable, high-throughput workloads, node-based clusters are often significantly cheaper. AWS's own Example 2 shows a spiky workload costing $2.92/hour serverless vs. $5.66/hour on-demand nodes — but for steady traffic, the math can flip the other way.&lt;/p&gt;

&lt;p&gt;The minimum charge matters too. A Serverless cache for Redis OSS or Memcached is metered for at least 1 GB of data stored — roughly $91/month minimum even if you're storing almost nothing. Valkey's 100 MB minimum brings that floor down to about $6/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extended Support: The Surcharge Nobody Budgets For
&lt;/h2&gt;

&lt;p&gt;When a Redis OSS or Memcached engine version reaches end-of-life, AWS continues providing security patches through Extended Support — at a steep premium.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Surcharge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Year 1–2 after EOL&lt;/td&gt;
&lt;td&gt;80% premium on node-hour rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Year 3 after EOL&lt;/td&gt;
&lt;td&gt;160% premium on node-hour rate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A cache.m5.large running Redis 5 (EOL January 31, 2026) at $0.156/hour becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Year 1–2:&lt;/strong&gt; $0.156 + ($0.156 × 80%) = &lt;strong&gt;$0.281/hour&lt;/strong&gt; (~$205/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Year 3:&lt;/strong&gt; $0.156 + ($0.156 × 160%) = &lt;strong&gt;$0.406/hour&lt;/strong&gt; (~$296/month)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's nearly triple the base cost by year three. Teams that don't track engine versions can drift into Extended Support without realizing their bill just jumped 80%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup Storage and Data Transfer
&lt;/h2&gt;

&lt;p&gt;Two cost categories that don't appear under the main "ElastiCache" line:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backup storage:&lt;/strong&gt; $0.085/GiB per month for all regions. No data transfer charges for creating or restoring backups. This is generally small unless you're snapshotting large clusters frequently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data transfer:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same AZ (EC2 ↔ ElastiCache)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-AZ (same Region)&lt;/td&gt;
&lt;td&gt;$0.01/GiB each way&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-Region (Global Datastore)&lt;/td&gt;
&lt;td&gt;$0.02/GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The cross-AZ charge is easy to miss because it shows up as EC2 data transfer on the bill, not ElastiCache. You're only charged for the EC2 side — there's no ElastiCache data transfer charge for traffic in or out of the node itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Tiering: The Cost Saver Most Teams Don't Know About
&lt;/h2&gt;

&lt;p&gt;R6gd nodes combine memory and NVMe SSD, automatically moving least-frequently-accessed data to SSD. You get nearly 5× the total storage capacity compared to memory-only R6g nodes.&lt;/p&gt;

&lt;p&gt;AWS's example: a 1 TiB dataset needs 1 cache.r6gd.16xlarge node ($9.98/hour) vs. 4 cache.r6g.16xlarge nodes ($21.01/hour) — a 52% cost reduction.&lt;/p&gt;

&lt;p&gt;The trade-off: SSD-resident data has slightly higher latency on first access. If your workload regularly accesses less than 20% of the dataset, data tiering is worth evaluating.&lt;/p&gt;

&lt;p&gt;Data tiering is not available with ElastiCache Serverless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reserved Nodes: Up to 55% Off
&lt;/h2&gt;

&lt;p&gt;If your ElastiCache usage is stable, reserved nodes offer steep discounts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Commitment&lt;/th&gt;
&lt;th&gt;Discount vs. On-Demand&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1-year, No Upfront&lt;/td&gt;
&lt;td&gt;Up to 48.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-year, Partial Upfront&lt;/td&gt;
&lt;td&gt;Up to 52%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-year, All Upfront&lt;/td&gt;
&lt;td&gt;Up to 55%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reserved nodes are size-flexible — you can apply the discount across different node sizes within the same family. If you buy a reservation for cache.r7g.xlarge, it can cover cache.r7g.large nodes proportionally.&lt;/p&gt;

&lt;p&gt;One useful detail: Redis OSS reservations automatically apply to Valkey nodes in the same family and region. Since Valkey is 20% cheaper, you get 20% more value from existing reservations after migrating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Idle Caches
&lt;/h2&gt;

&lt;p&gt;Here's what actually burns money: caches nobody is using.&lt;/p&gt;

&lt;p&gt;ElastiCache has no scale-to-zero for node-based clusters. A cache with zero hits costs exactly the same as one handling millions of requests. This is the pattern we see most often:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A team provisions a cache for a microservice, then the service is deprecated&lt;/li&gt;
&lt;li&gt;Dev/staging caches left running after the project ends&lt;/li&gt;
&lt;li&gt;A "temporary" cache for a migration that became permanent infrastructure&lt;/li&gt;
&lt;li&gt;A replicated cluster in non-production where a single node would suffice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A three-node cache.r7g.xlarge cluster running idle for a year at Valkey on-demand rates: &lt;strong&gt;$9,186 wasted&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over-Provisioned Caches Are Nearly as Bad
&lt;/h2&gt;

&lt;p&gt;Beyond idle caches, oversized nodes are the second biggest source of waste. Teams pick a large node type during initial setup, the workload stabilizes at a fraction of capacity, and nobody revisits the sizing.&lt;/p&gt;

&lt;p&gt;A cache.r6g.xlarge running at 6% CPU with active connections is doing real work — but it's doing it on a node that's 3–4× larger than needed. Downsizing from cache.r6g.xlarge to cache.r6g.large can cut costs by 40–50% with no performance impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Spot the Waste
&lt;/h2&gt;

&lt;p&gt;Check these CloudWatch metrics for each cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CacheHits:&lt;/strong&gt; Zero for 14+ days means nothing is reading from this cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CurrConnections:&lt;/strong&gt; Zero means nothing is even connecting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EngineCPUUtilization:&lt;/strong&gt; Consistently under 10% with active connections means the node is oversized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quick CLI inventory of all your ElastiCache clusters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws elasticache describe-cache-clusters &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--show-cache-node-info&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'CacheClusters[*].{
    ClusterId:CacheClusterId,
    Engine:Engine,
    EngineVersion:EngineVersion,
    NodeType:CacheNodeType,
    NumNodes:NumCacheNodes,
    Status:CacheClusterStatus
  }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any of those clusters show an engine version approaching EOL, you're on the clock for an Extended Support surcharge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloudcostwise.io/" rel="noopener noreferrer"&gt;CloudWise&lt;/a&gt; detects idle ElastiCache clusters by analyzing CloudWatch cache hit metrics over 14 days, flags oversized nodes running under 10% CPU, and alerts you when clusters are approaching or already incurring Extended Support surcharges. Three detectors, one scan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloudcostwise.io/" rel="noopener noreferrer"&gt;CloudWise&lt;/a&gt; automates AWS cost analysis across 180+ waste detectors. Try it at &lt;a href="https://cloudcostwise.io/" rel="noopener noreferrer"&gt;cloudcostwise.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>elasticache</category>
      <category>redis</category>
      <category>valkey</category>
    </item>
    <item>
      <title>The Most Wildfire-Prone Zip Codes in America — What USFS Data Shows</title>
      <dc:creator>Metra</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:28:52 +0000</pubDate>
      <link>https://forem.com/openmetrics/the-most-wildfire-prone-zip-codes-in-america-what-usfs-data-shows-1ghh</link>
      <guid>https://forem.com/openmetrics/the-most-wildfire-prone-zip-codes-in-america-what-usfs-data-shows-1ghh</guid>
      <description>&lt;p&gt;Climate change has made wildfire risk a major concern for homeowners across the western US. I pulled data from the US Forest Service and NIFC to build a risk lookup tool for every zip code in America.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Highest-Risk Areas
&lt;/h2&gt;

&lt;p&gt;Using USFS Wildfire Risk to Potential Structures data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;California&lt;/strong&gt; dominates the high-risk list, but it's not alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parts of &lt;strong&gt;Colorado&lt;/strong&gt; (particularly the Front Range foothills)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oregon and Washington&lt;/strong&gt; (especially the eastern slopes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Montana and Idaho&lt;/strong&gt; (wildland-urban interface areas)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arizona&lt;/strong&gt; (Prescott, Flagstaff areas)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Texas&lt;/strong&gt; (central hill country)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters for Homeowners
&lt;/h2&gt;

&lt;p&gt;Wildfire risk directly affects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Home insurance premiums&lt;/strong&gt; — some areas have seen 200-400% increases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property values&lt;/strong&gt; — high-risk areas are starting to see price adjustments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insurability&lt;/strong&gt; — some carriers are dropping coverage entirely in extreme-risk zones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mitigation requirements&lt;/strong&gt; — defensible space rules, fire-resistant materials&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the Data Shows
&lt;/h2&gt;

&lt;p&gt;The tool I built combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;USFS fireshed assessments&lt;/strong&gt; — risk scores based on vegetation, terrain, and fire history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical fire data&lt;/strong&gt; — from NIFC incident records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community risk ratings&lt;/strong&gt; — composite scores accounting for exposure and vulnerability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each zip code page shows the risk level, contributing factors, nearby area comparisons, and what the score means for insurance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Your Zip Code
&lt;/h2&gt;

&lt;p&gt;Free at &lt;a href="https://wildfire-risk.pages.dev/" rel="noopener noreferrer"&gt;wildfire-risk.pages.dev&lt;/a&gt; — enter any US zip code to see the wildfire risk assessment. Data from &lt;a href="https://www.usgs.gov/" rel="noopener noreferrer"&gt;USFS&lt;/a&gt; and &lt;a href="https://www.nifc.gov/" rel="noopener noreferrer"&gt;NIFC&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Built with Astro and Cloudflare Pages as part of a series of &lt;a href="https://solar-roi-13a.pages.dev/about/" rel="noopener noreferrer"&gt;free tools from government data&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Risk data from USFS Wildfire Risk to Potential Structures assessments. For official guidance, consult your local fire department and insurance provider.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Same Job, Different City: The Salary Gaps Are Bigger Than You Think</title>
      <dc:creator>Metra</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:28:48 +0000</pubDate>
      <link>https://forem.com/openmetrics/same-job-different-city-the-salary-gaps-are-bigger-than-you-think-2ehb</link>
      <guid>https://forem.com/openmetrics/same-job-different-city-the-salary-gaps-are-bigger-than-you-think-2ehb</guid>
      <description>&lt;p&gt;What if the same job pays twice as much just by crossing a state line? I pulled official Bureau of Labor Statistics salary data for 30 occupations across 20 major US cities to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Biggest Pay Gaps
&lt;/h2&gt;

&lt;p&gt;Using BLS Occupational Employment and Wage Statistics (OEWS) data, here are some salary differences that stood out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;San Francisco: $160,000+ median&lt;/li&gt;
&lt;li&gt;Dallas: ~$110,000 median&lt;/li&gt;
&lt;li&gt;Same job title, same skills, 45% pay difference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Registered Nurses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;California metros consistently pay $30,000-40,000 more than Southern cities&lt;/li&gt;
&lt;li&gt;But California's cost of living eats most of that difference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Accountants:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New York: ~$95,000 median&lt;/li&gt;
&lt;li&gt;Atlanta: ~$72,000 median&lt;/li&gt;
&lt;li&gt;A 30% gap for the same certification&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  But Salary Alone Is Misleading
&lt;/h2&gt;

&lt;p&gt;Raw salary numbers don't tell the whole story. That's why I built a &lt;a href="https://salary-by-city.pages.dev/" rel="noopener noreferrer"&gt;take-home pay calculator&lt;/a&gt; that factors in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Federal taxes&lt;/strong&gt; (2026 brackets by filing status)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State income tax&lt;/strong&gt; (0% in Texas/Florida vs 13%+ in California)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;401(k) contributions&lt;/strong&gt; (pre-tax deductions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost of living context&lt;/strong&gt; (rent as % of take-home pay)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A $160K salary in San Francisco with 13% state tax and $3,200/month rent leaves you with roughly the same disposable income as $110K in Dallas with 0% state tax and $1,700/month rent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data
&lt;/h2&gt;

&lt;p&gt;I used the &lt;a href="https://www.bls.gov/oes/" rel="noopener noreferrer"&gt;BLS OEWS&lt;/a&gt; survey, which covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Median salary, 25th and 75th percentiles&lt;/li&gt;
&lt;li&gt;Entry-level and senior salary estimates&lt;/li&gt;
&lt;li&gt;Employment counts per metro area&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool covers 600+ job-city combinations. Every page has the tax calculator so you can adjust for your personal situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Free at &lt;a href="https://salary-by-city.pages.dev/" rel="noopener noreferrer"&gt;salary-by-city.pages.dev&lt;/a&gt; — search by job title or city. The take-home calculator adjusts for filing status, state tax, and 401(k). No signup needed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All salary data from BLS OEWS May 2024 release. Tax calculations use estimated 2026 federal brackets.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>data</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ZeroID: Identity Infrastructure for Autonomous AI Agents</title>
      <dc:creator>Kunal Kumar</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:28:34 +0000</pubDate>
      <link>https://forem.com/kunal_kumar_59fde2eb2feb3/zeroid-identity-infrastructure-for-autonomous-ai-agents-a9k</link>
      <guid>https://forem.com/kunal_kumar_59fde2eb2feb3/zeroid-identity-infrastructure-for-autonomous-ai-agents-a9k</guid>
      <description>&lt;p&gt;Building Autonomous AI agents is getting easier&lt;br&gt;
Knowing which agent did what, on whose authority, and with what permissions is still messy.&lt;/p&gt;

&lt;p&gt;We’ve been working on ZeroID, an open-source identity layer for AI agents that gives every agent its own cryptographically verifiable identity, supports agent-to-agent delegation, and enables real-time revocation.&lt;/p&gt;

&lt;p&gt;Why this matters for AI agent developers who want to run their autonomous agents in production:&lt;/p&gt;

&lt;p&gt;No more shared service-account style access&lt;br&gt;
Sub-agents get scoped, downscoped credentials&lt;br&gt;
Every action can carry an auditable delegation chain&lt;br&gt;
Better fit for MCP servers, tool-calling agents, and multi-agent workflows&lt;br&gt;
Under the hood, ZeroID is built around OAuth 2.1, RFC 8693 token exchange, and WIMSE/SPIFFE-style identities.&lt;/p&gt;

&lt;p&gt;If you’re building agentic systems and thinking about auth, delegation, trust, or audit-ability, do checkout:&lt;br&gt;
Repo: &lt;a href="https://github.com/highflame-ai/zeroid" rel="noopener noreferrer"&gt;https://github.com/highflame-ai/zeroid&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We would love your feedback. If you like it, do consider starring us 🤗&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
