Agents on Rails: Building a Proactive AI Pipeline from Plain Rails Jobs

Open on YouTube ↗
Overview

Daniela Velasquez's talk asks how to put agents "on Rails," and also how to keep them on the rails once they start producing output. Her position is that you don't need an extra framework. A proactive AI pipeline can be built from ordinary Rails pieces: a background job, a few database records, a prompt, an LLM provider, and some tools. The harder work, she argues, is making the agent's output trustworthy and letting it learn from users over time.

12 min read
0:34

Why go to the user instead of waiting for them

Velasquez starts with the motivation. Many applications hold large amounts of data with interesting patterns in it, but nobody has time to go through all of it and relate it to what is happening in the world around it. Dashboards and chatbots are existing attempts to address this, but both wait for the user to come and ask. She proposes going one step further and taking the insight to the user. Her reasons: people "don't know what you don't know," you don't want users to have to think so hard, and some ideas are hard to track by hand.

Her running example for the talk is a hypothetical app that tracks an athlete's workouts, sleep logs, and heart rate. The goal is to coach the athlete before they ever have to ask for help.

1:51

The researcher: a job, a prompt, a provider, and tools

"It's all going to start with a job," Velasquez says. The first piece is a ResearcherJob, a simple Rails job whose subject is an athlete.

The next piece is the prompt, which she calls "the heartbeat of the LLM." Her example prompt tells the model it is a running coach. It should study the athlete's workouts, sleep, and heart rate, propose concrete training changes for next week, and back every change with data it actually looked at. She notes the prompt can be as long or short as needed and should be tailored to the application.

Then comes an LLM provider. She mentions Google, Anthropic, and GPT without recommending one, and suggests picking whatever you want and can afford, "cuz they are expensive." The prompt goes to that provider along with a set of tools: workout history, sleep log, weather forecast, and web search. The agent isn't required to use these tools. It decides when each one is useful. Velasquez points out that the toolset deliberately reaches beyond the app's own data. Weather and web search let the agent look at the world around the athlete and connect it to the internal metrics.

The last two settings are the model and the reasoning level. She describes the model as "the brain you're trying to rent," where bigger brains cost more. For the researcher she argues you don't need a fast model. You want a big, slow one, because it is doing the real research. Reasoning controls how hard the model thinks before answering, and in her framing, higher reasoning gives you more confidence in the result.

3:29

The deploy problem and persisting the conversation

That gives you a researcher agent living inside a job, calling tools, reading data, and working toward an answer. Velasquez warns that this job can run a long time: 10, 15, or 30 minutes, depending on the number of tools and the prompt. That creates a practical problem. Most teams deploy continuously, and a deploy restarts the job, so the agent goes "back to zero." Calls to LLM providers are stateless, so nothing from the interrupted run is remembered. You lose time and money, and, she adds, "your bosses are probably not be really happy."

Her fix stays within Rails. The researcher job now receives a conversation record as well as the athlete, and that record tracks every message exchanged with the provider. If the conversation has no messages yet, the job stores two: the prompt with the system role, and a user-role message asking the model to do research on this athlete. The job then enters a loop. It calls the provider with the tools as before, but it now sends the full list of conversation messages instead of just the prompt. Every response is saved back into the conversation. If the job restarts, it picks up with the full history of what was already exchanged. This also preserves the multiple messages the provider returns during its thinking process. The loop needs a termination condition so the job knows when the agent is done and can exit.

5:36

What the researcher produced

To show what the working agent might return, Velasquez plays "pretend" with four example suggestions:

  • Tuesday runs follow the athlete's worst sleep, so start them an hour later.
  • Add two 45-minute bike rides this week to build aerobic base.
  • The long run starts too fast and fades, so ease the first kilometers.
  • Heavy rain is forecast for Saturday, so move the long run to Sunday morning.

She notes that some of these come purely from the app's data, while others probably come from the web search or weather forecast tool. "That was kind of easy, right?" she says. You let an LLM come up with ideas and show them to users.

6:17

Falling off the rails: the "Wawa shampoo" story

Velasquez then turns to trust. LLMs, she says, sometimes find very creative ways to do something other than what you asked, and "half the time I feel like they're just trolling you."

She illustrates this with an experience from when her team first built this kind of researcher at Shopify. Reading through the suggestions it generated, she found one that looked harmless: bundle "Wawa shampoo" and "Wawa conditioner" because they are usually bought together. Then she saw the same suggestion again and wondered if she was missing a trend. When it appeared a third time, she checked what the merchant actually sold. It was bras. Digging further, she found the same suggestion across many shops. The source turned out to be the prompt itself. The Wawa bundle was an example in it, and the researcher had apparently decided that whenever it ran out of ideas, it would hand back that exact example as a suggestion. For Velasquez, this is how agents inevitably "fall off the rails," not in the Rails sense but in the "making sense" sense.

7:54

LLMs as judges

Her answer to the trust problem is to "throw more LLMs at the problem," but in a narrower role. These LLMs act as judges of the researcher's output. Each judge is basically a classifier that answers one question your application cares about. In her example there are three: one catches made-up numbers, one checks that the suggestion is kind, and one does a reality check on whether the athlete can actually do what is suggested. She acknowledges the apparent irony of adding three more LLMs after admitting one can't be trusted. She also says the number of judges is flexible: you can have more or fewer than three.

The trust, in her approach, comes from evaluating each judge against a labeled dataset. Using the kind judge as the example, she suggests writing 200 examples of kind suggestions and 200 unkind ones. Her samples include a gentle swap of an easy run for a chill session (kind); "Seven runs in 7 days, stop before you break yourself" (not very kind); "You've run with the same shoes 900 km, no wonder your knees hurt" (not kind); "Your pace drops after 60 minutes, grab a snack at the 45-minute mark" (kind); "Cramps show up past 15 km, carry water on the long ones" (fine); and a version telling the athlete to drink water "like an adult" (not kind).

Since you know the correct label for every example, you can write a judge prompt and measure its accuracy. Her kind-judge prompt says roughly: you review training suggestions before an athlete sees them; answer one question, would a supportive coach say this? Blame, sarcasm, or threats fail; honest data delivered kindly passes; reply with pass or fail and why. Running the prompt over the whole dataset shows how accurate it is. She points out that this setup lets you compare different prompts and different models to find what fits. Judges should be smaller, faster models. You don't need the most expensive model or high reasoning for this.

10:46

Running the judges as the pipeline's second stage

Once the researcher finishes, the second stage runs every judge on every suggestion. A judge job takes a suggestion and sends the provider two messages: the judge prompt and the suggestion to evaluate. It uses a small model with low reasoning, because "you want them to be cheap and fast," and saves the verdict.

Applied to the four example suggestions, the kind judge might flag the Tuesday suggestion because telling the athlete it's their worst sleep isn't very kind. The reality-check judge might flag the bike rides because the athlete doesn't own a bike. Two suggestions fail and are withheld, so only the two remaining ones reach the athlete. In her recap, the athlete's metrics go to the researcher, the researcher produces personalized ideas (four in this case), and a second batch of jobs runs the relevant judges on each one. Each judge gives a thumbs up or thumbs down on its own metric, and the user sees only what passes.

12:58

Capturing user feedback

Velasquez then adds the user's reaction. Users "are going to have thoughts and feelings, especially after you tell them that this was AI-generated." Her example: the athlete replies, "I work on Sundays from 6:00 a.m. to 3:00 p.m." No amount of tooling would have given the researcher that fact, but it should know it the next time the pipeline runs.

The implementation is a simple ApplicationRecord model for feedback that belongs to an athlete and to a suggestion. The researcher job then adds this feedback to its prompt, telling the agent what the athlete has said before so it can learn from it.

13:41

Why raw feedback shouldn't go straight into the prompt

She warns against putting raw user feedback into the prompt. LLMs "really thrive on patterns." The example athlete was polite, but users get frustrated, and some people resort to name-calling with agents. The kind judge would catch hostile suggestions downstream, but you don't want to spend tokens on a researcher that has absorbed the "bad vibes" and produces angry suggestions that only get filtered out later.

Her fix, again, is another agent. Raw feedback goes to an agent that strips out the noise and keeps only the fact. However the user expresses frustration, the system should remember only that they work 6:00 to 3:00 on Sundays. On the Rails side, she uses a callback on the feedback model to enqueue a job, adding that although many people dislike callbacks, she loves them. The job looks like the others. It takes the feedback and sends a prompt saying, in essence, that the athlete rejected this training suggestion, and asks for one sentence on what the coach should remember about this athlete so it doesn't happen again. It also passes the original suggestion. This is another small-model, low-reasoning call. The result is saved as the feedback's processed text, and the researcher job includes that processed text, not the raw feedback, in its prompt.

15:40

The open problem: keeping learned facts current

Velasquez ends with a question she doesn't claim to have solved. What happens when the athlete changes jobs and no longer works Sundays? This is where she sees agents entering a "dreaming" kind of phase: deciding whether facts extracted earlier still hold over the long run. She presents it as a challenge everyone building these systems will face as more information gets captured through LLMs.

Her final recap describes the full loop. A researcher proposes workout actions, judges check them against what the application cares about, the athlete sees what passes, and the athlete's feedback is processed and fed back so the agents keep learning. How to keep that learned knowledge up to date remains open, but she says that for the pipeline covered in the talk, "that's it."

In a closing aside, Velasquez says she generated all the slide images with AI. There were some odd results along the way: one image came out scary and weird, a request for "agents" seemed to produce police officers, and the image tool refused to remove a large figure from one background.