Agentic AI for eCommerce: what it means and how to build it on Rails and Solidus
TL;DR
Difficulty: Intermediate
An AI agent plans, decides and acts towards a goal in a loop: perceive, reason, act, observe.
- Where it helps a shop: restocking, proactive delay notices, order routing and pricing.
- Rails and Solidus fit well: Solidus events on Spree::Bus, recurring jobs in Solid Queue, and replaceable stock sorters and allocators.
- Guardrails come first: low risk actions run alone, medium risk with an alert, high risk waits for a person, and everything is logged.
- Start small: an agent that only watches, then one low risk agent such as cart recovery, measured before it scales.
Agentic is the word of 2026, and few people using it could define it. I explain what an AI agent really is, where agents already do useful work in an online shop, and how to build them on Ruby on Rails and Solidus with code checked against the current Solidus and Rails documentation, worked through on an invented Cork tea shop.
What "agentic" actually means, in plain English
Every few years a word takes over the tech world and gets stuck onto everything. In 2023 it was "generative". In 2024 it was "copilot". In 2025 and 2026 it is "agentic". Most people using it could not explain it if you put them on the spot, so let me do that, plainly.
An AI agent is software that can plan, decide and act towards a goal without a human telling it what to do at every step. The "agentic" part means it has agency. It makes decisions on its own, inside boundaries you set.
An analogy helps.
A chatbot is like a waiter. It takes your order to the kitchen and brings back what you asked for. It does not notice the kitchen is running low on salmon and suggest the chicken. It does what you tell it, one exchange at a time.
An automation rule is like the timer on an oven. It switches off at 6pm every day, whether anything is cooking or not. A fixed rule with no sense of context.
An AI agent is like a restaurant manager. It notices stock running low and orders from suppliers. It sees that table five has waited too long and moves a waiter. It spots that the salmon is not selling tonight and changes the specials board. Dozens of decisions an evening, each one based on what is actually happening.
Technically, an agent runs a loop with four steps.
The diagram reads from the top: the agent looks, thinks, acts, checks what happened, and starts again with that knowledge. The loop is the whole difference. A chatbot waits for input, answers and stops. No loop, no initiative. A chatbot is reactive, an agent is proactive.
An example: a customer of Glanmire Teas, an invented online tea shop in Cork, asks the shop's chatbot "where is my order?" and gets the tracking link. That is reactive. The shop's agent, meanwhile, has already seen that the courier scan is a day late, worked out that the parcel will miss Friday, and emailed the customer on Wednesday with the new date. Nobody asked it to.
Why this matters for eCommerce now
The forecasts are large, and they are forecasts, so read them as direction rather than fact. McKinsey estimated in October 2025 that agentic commerce could orchestrate as much as 3 trillion to $5 trillion globally.1 An IBM Institute for Business Value study in 2026 found that 45% of consumers already use AI for part of their buying journey.2 Gartner predicted in August 2025 that as many as 40% of enterprise applications would include task specific AI agents by the end of 2026, up from less than 5% in 2025.3
What that means for a shop is simpler than the numbers. The tedious operational work (watching stock, chasing couriers, nudging abandoned carts) is the work agents are good at. A business that hands it over can spend its people on the decisions that need judgement.
Let me walk through where agents already do useful work, one area at a time, with the Rails and Solidus code for each.
Agentic inventory management
Traditional inventory management is reactive. You notice something running low and reorder. A more organised shop has reorder points, a rule that says "if stock falls below X, order Y". But those rules are static. They do not know about seasonal peaks, a campaign about to launch, or a supplier whose lead time has crept up.
An agentic inventory system watches stock in real time, measures how fast each product sells, allows for upcoming promotions and supplier lead times, and decides when to restock. In a Rails and Solidus application it might look like this. Spree::StockItem and Spree::Promotion are Solidus models, while the scopes (low_stock, upcoming), the Supplier model and the services are the shop's own code.
class InventoryAgent
SAFETY_FACTOR = 1.3
def perceive
{
stock_items: Spree::StockItem.includes(:variant).low_stock, # custom scope
velocity: SalesVelocityService.calculate(period: 7.days), # units per day by variant id
promotions: Spree::Promotion.upcoming, # custom scope
lead_times: Supplier.active.pluck(:id, :avg_lead_days).to_h # custom model
}
end
def reason(obs)
obs[:stock_items].filter_map do |item|
variant = item.variant
velocity = obs[:velocity].fetch(variant.id, 0)
next if velocity.zero?
lead_time = obs[:lead_times].fetch(variant.supplier_id, 7)
boost = promotion_multiplier(variant, obs[:promotions])
days_of_stock = item.count_on_hand / velocity.to_f
next unless days_of_stock < lead_time * 1.5
quantity = (velocity * lead_time * SAFETY_FACTOR * boost).ceil
urgency = days_of_stock < lead_time ? :critical : :standard
{ variant:, quantity:, urgency: }
end
end
def act(decisions)
decisions.each do |d|
if d[:urgency] == :critical
PurchaseOrderService.create_and_send!(d[:variant], d[:quantity])
OperationsMailer.reorder_sent(d).deliver_later
else
PurchaseOrderService.create_draft(d[:variant], d[:quantity])
end
end
end
end
Notice the tiered autonomy. When the shop is about to run out, the agent sends the purchase order itself and tells the team. Otherwise it prepares a draft for a person to approve. The agent has agency, inside boundaries.
Worked example: one tea at Glanmire Teas
The example's assumptions: Glanmire's Irish breakfast blend sells 12 tins a day, there are 100 tins on hand, the supplier takes 10 days, and a promotion next week is expected to lift sales by half (a multiplier of 1.5).
Days of stock: days.
The trigger is 1.5 times the lead time, days. Since 8.3 is below 15, the agent reorders.
Quantity: tins.
Urgency: 8.3 days of stock is less than the 10 day lead time, so the shop will run out before new stock can arrive. That is critical, and the agent sends the order straight away and emails the team. Had there been 150 tins (12.5 days), the order would have gone to a person as a draft.
Agentic customer support
Most "AI customer support" in eCommerce today is a better chatbot. It understands language better than the old keyword bots, but it still waits for the customer to ask.
An agentic support system watches the customer journey and steps in first. Say an order is placed and the agent sees the warehouse is behind, so delivery will slip by two days. Before the customer notices, it writes: "Your order is being packed, but it will now arrive on Thursday rather than Tuesday. I've added 10% off your next order to say sorry." The customer did not complain. The agent perceived, reasoned and acted.
In Rails, the shape is the same loop. Solidus has no expected_delivery_at column or sentiment model, so those are the shop's own additions.
class CustomerExperienceAgent
def perceive
{
delayed_shipments: Spree::Shipment.ready.where("expected_delivery_at > promised_delivery_at"),
negative_sentiment: CustomerSentiment.where(score: ...(-0.5)),
abandoned_carts: AbandonedCart.recent.high_value
}
end
def reason(obs)
delays = obs[:delayed_shipments].map do |shipment|
{ type: :delay_notice, order: shipment.order, compensation: compensation_for(shipment.order) }
end
unhappy = obs[:negative_sentiment].pluck(:user_id)
carts = obs[:abandoned_carts].reject { |cart| unhappy.include?(cart.user_id) }.map do |cart|
{ type: :cart_recovery, cart:, incentive: incentive_for(cart) }
end
delays + carts
end
def act(decisions)
decisions.each do |d|
case d[:type]
when :delay_notice
CustomerMailer.delay_notice(d[:order], d[:compensation]).deliver_later
apply_compensation(d[:order], d[:compensation])
when :cart_recovery
CartRecoveryMailer.personalised(d[:cart], d[:incentive]).deliver_later
end
AgentAuditLog.create!(agent_name: self.class.name, action_type: d[:type], decision_payload: d)
end
end
end
One detail is worth copying: customers with a recent negative sentiment score are taken out of the cart recovery list. Nobody who has just been giving out about you wants a cheerful "you forgot something" email.
Worked example: what a delay notice costs and saves
The example's assumptions: Glanmire ships 1,200 orders a month, about 3% are delayed (36 orders), the average order is €32, and the agent gives 10% off the next order.
If every delayed customer uses the discount on a similar order, the cost is a month. If the shop judges that a silent delay loses one customer in six, and a customer is worth €90 of margin over their life, the silence costs . The agent pays for itself if the notice keeps even a quarter of those customers. Both the one in six and the €90 are this example's assumptions, and the first thing to do with a real agent is measure them.
Agentic order routing
If you sell from several warehouses, or use drop shipping suppliers, order routing is one of the most useful places for an agent.
Fixed routing says orders from Munster go to the Cork warehouse and orders from the west and north go to Galway. What happens when Cork is swamped, or out of the item, or a supplier could ship faster at lower cost? An agentic router scores every source on the current state of the network: stock, queue depth, shipping cost, delivery time and reliability.
Solidus gives you real hooks for this. Its stock configuration exposes a location filter, which receives the order, a location sorter, which sets the order in which stock locations are used, and an allocator, which decides how on hand and backordered stock are split.4 A sorter only has to implement sort over the stock locations it is given.5 So a scoring router can be a custom sorter:
module Glanmire
class ScoredLocationSorter < Spree::Stock::LocationSorter::Base
WEIGHTS = { speed: 0.3, cost: 0.3, reliability: 0.2, capacity: 0.2 }.freeze
def sort
stock_locations.sort_by { |location| -score(location) }
end
private
# Each metric is a number from 0 to 1, refreshed by a background job
def score(location)
metrics = LocationMetrics.for(location)
WEIGHTS.sum { |factor, weight| metrics.public_send(factor) * weight }
end
end
end
# config/initializers/spree.rb
Spree.config do |config|
config.stock.location_sorter_class = "Glanmire::ScoredLocationSorter"
end
The "agent" part sits around it. A background job keeps LocationMetrics current from what actually happens (how long each warehouse took, how many parcels arrived late), and a subscriber checks each finished order and sends anything unusual, such as a shipping cost far above normal, to a person.
The score is a weighted sum, a close relative of the weighted mean:
Here is the location's score, is the number of factors (four here), is the weight of factor (the weights add up to 1) and is the location's measurement on that factor, scaled from 0 to 1. Foundations: the mean
Worked example: Cork or Galway for a parcel to Limerick
The example's figures, each scaled from 0 to 1: Cork scores 0.5 on speed, 0.6 on cost, 0.9 on reliability and 0.3 on capacity (it is busy today). Galway scores 0.8, 0.7, 0.8 and 0.9.
This is Cork: is its score, each weight multiplied by its measurement .
And this is Galway, with its score from the same weights. Galway wins, 0.79 to 0.57, so its stock is used first. Tomorrow, when Cork has cleared its queue and its capacity is back to 0.9, Cork scores 0.69 and the gap narrows. The rule did not change. The world did.
Agentic dynamic pricing
Dynamic pricing is where agents get powerful, and dangerous without guardrails.
A pricing agent watches competitor prices, demand, stock, margin targets and the season, and adjusts prices towards a goal: revenue, margin or market share. Without limits, two pricing agents can chase each other to the bottom and wipe out both margins, or one can spike prices in a demand surge and damage trust. This is exactly the kind of multi agent conflict I've been working through with GrowCentric.ai, and I wrote about it in when your AI starts fighting itself.
The answer is clear boundaries, checked in the right order:
class PricingAgent
GUARDRAILS = {
min_margin: 0.15, # never below 15% margin
max_daily_change: 0.10, # at most 10% a day
approval_above: 0.20 # a proposal over 20% goes to a person
}.freeze
def propose_price(variant)
current = variant.price
optimal = calculate_optimal_price(variant)
proposed_change = ((optimal - current) / current).abs
if proposed_change > GUARDRAILS[:approval_above]
PricingApprovalQueue.enqueue(variant:, current:, proposed: optimal, reasoning: @reasoning)
else
update_price(variant, constrain(variant, current, optimal))
end
end
private
def constrain(variant, current, optimal)
step = current * GUARDRAILS[:max_daily_change]
price = optimal.clamp(current - step, current + step)
floor = variant.cost_price / (1 - GUARDRAILS[:min_margin])
[price, floor].max
end
end
The order matters. The earlier version of this code clamped the price to the 10% daily limit first and then asked whether the change was over 20%. After clamping, it never is, so the approval queue could never fire. The approval check has to look at what the agent wanted, not at what the guardrail allowed.
Worked example: a price rise on gift sets
The example's figures: a Glanmire gift set costs €12 to buy in and sells at €20.
The agent proposes €23. The change is , under the 20% approval line, so the agent may act alone. The daily limit is , so the price moves to €22, not €23. The margin floor is , far below €22, so it does not bind. Tomorrow the agent may take the next step if the evidence still supports it.
If it had proposed €26 (a 30% change), the proposal would go to a person, with the agent's reasoning attached, and the price would not move until someone said yes.
How this maps to Rails and Solidus
If you already build on Ruby on Rails and Solidus, you are in a good position for agentic patterns, for four reasons.
Events are the perception layer. Solidus publishes events on its own bus, Spree::Bus, built on the Omnes library, which has been the default since Solidus 3.2. Earlier versions used a system built on ActiveSupport::Notifications.6 Core events include order_finalized, carton_shipped and order_canceled, and an agent subscribes like this:
# app/subscribers/kettlewell/agent_subscriber.rb
module Glanmire
class AgentSubscriber
include Omnes::Subscriber
handle :order_finalized, with: :route_order
handle :carton_shipped, with: :track_delivery
def route_order(event)
OrderReviewJob.perform_later(event.payload[:order].id)
end
def track_delivery(event)
DeliveryWatchJob.perform_later(event.payload[:carton].id)
end
end
end
# config/initializers/omnes.rb
Rails.application.config.to_prepare do
Glanmire::AgentSubscriber.new.subscribe_to(Spree::Bus)
end
The subscribers only enqueue jobs. The checkout should never wait for an agent to think.
Background jobs are the loop. Agents run on a schedule without blocking the shop. Since Rails 8, Solid Queue is the default Active Job backend, and it reads recurring tasks from config/recurring.yml.7 Sidekiq and GoodJob do the same job. A recurring task is safer than a job that re enqueues itself at the end, because a failed run cannot start a second chain beside the first.
# config/recurring.yml
production:
inventory_agent:
class: InventoryAgentJob
schedule: every 15 minutes
class InventoryAgentJob < ApplicationJob
queue_as :agents
def perform
agent = InventoryAgent.new
agent.act(agent.reason(agent.perceive))
end
end
The code is yours. Solidus is open source under a BSD licence,8 so you can read and replace every model and service: the stock sorter above, the order state machine, promotion rules, the checkout. On a hosted platform you work through whatever extension points the vendor provides, which is a grand trade for many shops and a constraint for an agent that needs to reach deep into the engine.
The API is there. Solidus ships a REST API, documented in OpenAPI.9 A GraphQL API exists as a separate extension, solidus_graphql_api, whose last release was version 0.3.1 in August 2023,10 so it is a thinner base for new work than the core REST API, which is enough for agents running as separate services.
Building agentic systems into a SaaS: GrowCentric.ai
My work on GrowCentric.ai is shaped by these patterns. GrowCentric is a SaaS platform that uses machine learning to optimise eCommerce marketing campaigns, and at its core it is a multi agent system.
The analytics agent monitors campaign performance, spots anomalies and trends, and raises them as they happen rather than in a weekly report.
The budget allocation agent moves spend from weaker campaigns to stronger ones, respects daily and monthly limits, and allows for diminishing returns at scale.
The audience agent analyses customer behaviour, finds high value clusters and lookalike patterns, and recommends new targeting.
The conflict resolution agent deals with the problem of competing shops using the same optimisation algorithms and bidding against each other. It detects when clients compete for the same audiences, segments to reduce the overlap, and uses game theory to look for outcomes that work for everyone.
All of them are built on Rails, with the event, background job and API patterns described above. The key decision was to design each agent as a standalone service with clear inputs, outputs and boundaries. They coordinate through a shared event bus (ActiveSupport::Notifications inside the monolith, message queues for the distributed parts), and every decision is logged.
The honest limitations, and why guardrails matter
I'd be doing you a disservice if I skipped what goes wrong. The failure modes are real.
Cascading errors. A bad decision spreads fast. An inventory agent that misreads demand over orders. A pricing agent that misreads a competitor wrecks margin. A person's mistake usually gets caught in a review. An agent can make hundreds before anyone looks.
The answer is tiered autonomy:
Read it from the top: a decision that costs little and can be undone runs on its own, one inside the limits runs with an alert, and anything else waits for a human. Whatever the route, it ends in the log.
No common sense. An agent optimises inside its parameters without the context a person has. It might promote a recalled product, route parcels into a region hit by floods, or send an upsell to someone who complained an hour ago. The answer is richer context and explicit blocklists: feed agents the wider state of the business, and name the situations where automatic action is off.
Over reliance and lost skills. If agents do everything, the team forgets how to run the shop when the agent is down, and it will be down sometimes, through API outages, a worse model or an edge case. The answer is graceful degradation. If the inventory agent stops, static reorder points take over. If the pricing agent fails, prices freeze. If the router breaks, orders go to the default warehouse.
Regulation. As I covered in my posts on the Cyber Resilience Act and the NIS2 Directive, businesses in the EU, and that means every Irish shop, face growing duties around security and data, and the EU AI Act adds its own. An agent that decides prices, writes to customers or processes personal data needs to be auditable and explainable. That means logging every input, decision and output, a human override, and plain documentation of how each agent decides.
Getting started: a practical roadmap
If you run a shop on Rails and Solidus and want to start, here is the path I'd recommend.
Start with observation, not action. Build an agent that watches and alerts, and does nothing else. Let it watch stock, fulfilment times and customer mood. Trust its perception before you let it act.
Pick one low risk, high value area. Cart recovery is a good first agent. A poor recovery email costs little. A good one pays.
Build the guardrails on day one. Do not build a fully autonomous agent and bolt limits on later. Decide what it may do alone, what it does with an alert and what needs approval, and make those limits configuration rather than code.
Log everything. Every perception, decision and outcome. You will need it for debugging, for compliance and for improving the agent.
class AgentAuditLog < ApplicationRecord
belongs_to :agentable, polymorphic: true, optional: true
validates :agent_name, :action_type, :decision_payload, presence: true
scope :recent, -> { where(created_at: 24.hours.ago..) }
scope :by_agent, ->(name) { where(agent_name: name) }
scope :escalated, -> { where(escalated: true) }
end
Measure the impact without mercy. Before an agent goes live, decide how you will judge it: revenue, cost saved, customer satisfaction. If it cannot show value in a reasonable time, switch it off.
Worked example: is a cart recovery agent worth it?
The example's assumptions: Glanmire sees 400 abandoned carts a month with an average value of €45, the agent recovers 5% of them, and the gross margin on tea is 40%.
Recovered carts: orders a month.
Recovered revenue: a month.
Margin: a month, or €4,320 a year.
If building and running the agent costs less than that in its first year, it earns its place. If the real recovery rate turns out to be 2%, the margin is a month, and the decision may change. That is why you measure before you scale. The same logic, with probabilities on each outcome, is expected value.
The move from traditional to agentic commerce will not happen overnight, but it is happening. A shop that starts now, even with one small, well guarded agent, will be ahead when it becomes the normal way to run.
Footnotes
-
Mark Brohan, McKinsey forecasts up to $5 trillion in agentic commerce sales by 2030, Digital Commerce 360, 20 October 2025, reporting McKinsey and Company, The agentic commerce opportunity, October 2025, https://www.digitalcommerce360.com/2025/10/20/mckinsey-forecast-5-trillion-agentic-commerce-sales-2030/
-
Amanda McGrath and Amanda Downie, What is agentic commerce?, IBM Think, 23 January 2026, citing a 2026 IBM Institute for Business Value study, https://www.ibm.com/think/topics/agentic-commerce
-
Melvin Hipolito, AI agents to power 40% of enterprise apps by 2026, says Gartner, IT Brief Australia, 27 August 2025, reporting the Gartner press release of 26 August 2025, https://itbrief.com.au/story/ai-agents-to-power-40-of-enterprise-apps-by-2026-says-gartner
-
Solidus, core/lib/spree/core/stock_configuration.rb, GitHub, accessed November 2026, https://github.com/solidusio/solidus/blob/main/core/lib/spree/core/stock_configuration.rb
-
Solidus, Spree::Stock::LocationSorter::Base, GitHub, accessed November 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/stock/location_sorter/base.rb
-
Solidus Guides, Subscribing to events, accessed November 2026, https://guides.solidus.io/customization/subscribing-to-events
-
Ruby on Rails Guides, Active Job basics, accessed November 2026, https://guides.rubyonrails.org/active_job_basics.html
-
Solidus, LICENSE, GitHub, accessed November 2026, https://github.com/solidusio/solidus/blob/main/LICENSE
-
Solidus, solidus_api README, GitHub, accessed November 2026, https://github.com/solidusio/solidus/blob/main/api/README.md
-
Solidus, solidus_graphql_api repository and releases, GitHub, latest release v0.3.1, 25 August 2023, https://github.com/solidusio/solidus_graphql_api