Ten Years of Rails at Phamily: Upgrades, Action Responders, SLA-Named Queues, and Tests as Theory

Open on YouTube ↗
Overview

Bryce Harlan, a senior principal engineer at Jaan Health, has spent about ten years working on the Ruby on Rails platform behind Phamily. Phamily is a text-based care management tool that helps clinical practices look after patients between office visits. In this episode of On Rails, host Robby Russell, who runs the Rails consultancy Planet Argon, talks with Bryce about what it takes to keep a long-lived Rails app healthy in a regulated industry. Topics include a risky jump from Rails 4 to Rails 6, a homegrown abstraction the team calls "action responders," a background-job setup that sends around 100,000 SMS messages a day, and how AI-assisted development is changing what the team expects from engineers.

29 min read

Bryce's position throughout is practical. They value Rails because it keeps the team focused on the problem in front of them. They are willing to build custom pieces when a generic tool gets stretched too far. More and more, they see the test suite, not the code, as the real statement of what a system is supposed to do.

Coming to Rails from Flask, Node, and math

Bryce had not used Ruby or Rails before joining the company, almost exactly ten years before the recording. Their earlier jobs were at companies whose main web apps were written in Flask, with some Node.js services at one of them. Before that, Bryce says, they were "more on the math side of things," the kind of programmer whose variables are all single letters.

That background shaped their first impression of Rails. In earlier stacks, much of the work felt "a little bit obtuse": handling middleware and all the layers that make a web app a web app. As a math person, Bryce just wanted to deal with what mattered. Rails let them "focus on the thing that I'm being tasked with building," not the plumbing. Asked what keeps the team on Rails now, Bryce points to the same things: a lively and effective ecosystem, fast iteration, and less time spent on web-app mechanics. They also admit that after ten years, "there's a little bit of inertia there."

The MVC architecture felt familiar immediately; Bryce could map each piece onto concepts they already knew. Ruby's syntax was the unfamiliar part ("where are all the parentheses?"), along with blocks, but they call that a welcome change.

Why Active Record still matters to them

Active Record surprised Bryce the most. Around 2014–2015 they were used to writing a lot of raw SQL, and an ORM that made queries "feel a lot more like code" was a big change.

That appreciation has grown. Bryce gives a recent example: they helped the company's AI team build a web server around its Python code using FastAPI and SQLModel. In that setup, they say, the developer has to manage a lot of indirection between the code and the database. Teams end up building dedicated service layers that amount to "rewriting what feels like in Rails, big parts of Active Record just in your codebase" just to handle basics like listing, sorting, ordering, and chaining scopes.

Inside Phamily, Bryce says Active Record is probably the tool the team leans on most. It is especially useful for views with complicated filters, where each filter can be an atomic scope and the scopes can be chained. "The more I use it, the more I'm kind of rewarded by using it," they say. Working in SQLModel felt like missing "a critical tool in my tool belt."

What Phamily does and who uses it

Phamily is a web app that clinical practices buy to manage care between visits. Bryce describes this as the care that happens outside the few days a year a patient sees a doctor, or the one day, or for many patients no days at all. Examples include medication questions and symptom reports.

The main users are nurses and care managers. Physicians mostly oversee the work, though at smaller clients they may be more hands-on. Care managers put patients on clinical protocols, so each patient gets a custom pathway. Bryce says the care manager acts almost like a quarterback. Phamily serves many older patients who see several doctors and take many medications, so they need one person they can ask things like how to get something approved by insurance or where to find their latest blood test results.

Care managers also do a lot of proactive outreach. Bryce notes that many patients want to stay independent and feel the healthcare system is "not the most welcoming space." Some assume that if they asked for help they wouldn't get it, so they don't ask. Reaching out first builds the relationship. In the app itself, the day-to-day work is mostly messaging patients, reviewing and updating care plans, and sending documentation back to systems of record.

Working under HIPAA: more scrutiny, not more fear

Robby asks whether healthcare is an intimidating place to work, given HIPAA. Bryce had not worked in healthcare before; their previous job was at a 3D graphics startup. Still, they wouldn't call it daunting. There are plenty of compliance trainings. Bryce spends "basically a weekend" each year clicking through scenarios and signing forms confirming they understand the rules, which they consider "not a big deal usually."

The bigger change is on the technical side. More people review your work with a critical eye than you might be used to. Bryce sees that as a good thing, because it gives an explicit chance to slow down and make sure the work is secure. At a fast-growing startup, they point out, security is often not top of mind. Much of the low-level work of securing deployments is handled by the hosting provider Aptible and by a separate platform team at Jaan Health. Bryce says they aren't the right person for detailed platform questions.

SMS first, and why there's no patient app

Architecturally, Rails is the central hub and API, with a few spoke services around it. Bryce estimates that about 80% of "the magic" happens in the Rails layer. Patient communication runs roughly 90/10 in favor of SMS over a patient web portal. The portal is its own small, lightweight Rails app. SMS delivery goes through Twilio, and Rails orchestrates the Twilio calls.

The SMS focus comes from the company's history. When Bryce joined, the company sent appointment reminders, and texting was the natural channel; asking patients to open a link or install an app just to confirm an appointment wasn't practical. About six months after Bryce joined, the company pivoted to care orchestration and care management. The team had noticed that front-desk staff were already forming relationships with patients over these texts, the same kind of relationships Phamily now tries to encourage. Bryce calls this an emergent behavior from users that the company then leaned into.

People inside and outside the company regularly ask for a patient mobile app. Bryce says the idea isn't ruled out, but the team treats not requiring one as a differentiator. The CEO describes it as "meeting the patients where they are": people already have a phone and shouldn't have to download an app unless they want to. Bryce adds that about half of their mom's phone calls to them are about troubleshooting a login for some app she needs.

On compliance, any data sent to an outside system must be covered by a Business Associate Agreement (BAA), a legal agreement guaranteeing certain HIPAA properties such as encryption at rest. Patients have to opt in to SMS. If a patient hasn't given permission, they are directed to the web portal instead.

Jumping from Rails 4 to Rails 6 in spring 2020

The app was started by the founders a year or two before Bryce joined, and at that point it ran on Rails 4. It stayed on Rails 4 for a long time. Today about 15 engineers work on the codebase, and nearly all of them touch the Rails app, though some of the AI team members do so less often. The app is now on Rails 7, and the team is in the early stages of evaluating Rails 8.

The route there was, in Bryce's words, "unconventional," and they don't necessarily recommend it. The team went directly from Rails 4 to Rails 6 in spring 2020. Bryce says the timing was actually part of the reason. The company was still small, and many people had been "skeptical to say the least" about virtual care in clinical settings. Then, almost overnight, the company was "swimming in business."

Bryce describes the codebase at that time as "a bit of a Frankenstein of gems," the result of an early-stage company shipping features while searching for product-market fit. Looking at the app, the team decided they might get only one chance to make a big change. If they waited, the upgrade would soon become a six-month project. So they chose to "rip the band-aid off" and deal with any fallout afterward.

Asked why they stayed on Rails 4 so long, Bryce names a small team with little appetite for paying down tech debt, plus the lack of any Rails 5 feature they were eager for. Bryce recalls Action Cable as the headline Rails 5 feature. It was "if it isn't broke, don't fix it." Rails 6 had been out for about half a year when they moved.

Bryce is frank about the risk. Test coverage was "certainly poor," and that is the main reason they wouldn't recommend the approach. The team handled it by treating the upgrade almost like starting over: they gutted much of the application and rewrote it from scratch in Rails 6. Bryce says this made the transition somewhat smoother.

A decision that aged well: owning the conversation model

Asked which decisions have held up, Bryce first mentions that being on current software made security conversations much easier. The more interesting example is about a gem.

Before the upgrade, the team used Mailboxer to represent conversation state. Mailboxer is designed for email, and Phamily was not an email platform. Bryce says gems are great and the team uses them all the time, but as a product matures, a gem that becomes too load-bearing gets tortured "into these shapes that it was never really designed for." During the rewrite, the team dropped Mailboxer, kept the ideas they liked from it, and designed their own models. That is still the architecture they use, which is why Bryce says it has "aged pretty well."

Choosing gems, especially now that AI is involved

Robby asks whether the team now thinks differently about bringing in dependencies versus building its own. Bryce says AI "has almost added a whole another dimension" to that decision. Earlier, the team tried to assemble the app from well-tested, well-understood, well-documented pieces of code.

The team still prefers gems in two situations. The first is when the problem is truly a commodity; they aren't writing their own auth layer, for example. The second is when the gem has an active community and is reasonably well maintained. Bryce also likes to read the gem's source. They see poking around inside other people's code as one of the best ways to learn any language, especially Ruby, and a quick way to judge whether code is well written and whether they like it.

Rails as an API behind Vue front ends

Rails now runs purely as an API, in API mode, and all front ends are built in Vue. Originally the views were server-rendered HAML. Bryce says the team began facing questions about reactivity that it had no good answers for on Rails 4, since tools like Action Cable weren't part of their stack. They picked Vue for reasons similar to why the company picked Rails. They liked Vue's single-file components and felt the framework had a strong opinion about how front-end code should be written.

Asked whether they would choose differently today, Bryce says they're curious. Having a single stack has an elegance that appeals to them, and if starting over they would at least try what the latest Rails offers. They haven't worked much with recent Rails front-end changes, though. They also note that front-end code tends to grow quickly and that declarative front-end programming seems to have become the industry standard. "Nobody's really writing jQuery frontends anymore."

The API has no external consumers yet. Bryce says it is one of the most common requests, especially from large health systems with their own IT teams who want an API key so they can build their own tools. The team hasn't prioritized it because of the security work it would require, but Bryce expects it to happen eventually.

Action Responders: thin controllers taken to an extreme

Robby asks what Bryce would tell a new hire in a 30-minute handoff. Bryce picks the action responder, a pattern the team built themselves. They call it bespoke but say it's "built in the spirit of Rails." They describe it as taking the Rails idea of thin controllers and fat models "almost to its logical extreme."

Many of the app's APIs go through a single API endpoint, which hands off to a thin abstraction layer: the action responders. Bryce's short explanation is that an action responder "is basically a controller." It takes care of data serialization and standardizes parameter parsing, so individual endpoints don't have to. The same interface is used whether the call comes in over HTTP or from other backend code, such as a service calling it directly. The arguments are the same either way, and the front end always receives a standardized, normalized payload.

The API follows JSON:API, so clients can request sparse fieldsets and specific associations. Any endpoint built on action responders supports this automatically; if the client wants only one field, it gets only one field. Bryce says it's "a little bit like GraphQL in a lot of ways." The team did seriously consider GraphQL before building action responders. They decided against it because GraphQL seemed like a lot of overhead, they didn't need most of its features, and it "didn't feel like the Railsy way." What they wanted was to take logic out of controllers and move it closer to the model, not spread it across "a litany of service objects."

Bryce explains what prompted the change. The team still uses service objects, but controllers had turned into thin wrappers around models, and a collection of service objects was handling the same concerns over and over. Parameter parsing was one. Authorization was another: the team uses Pundit, and authorization logic kept getting repeated. Bryce notes that authorization is "a big deal in our environment." Much of this work was done the same way each time, but there was no abstraction separating the generic parts from the model-specific parts.

Asked whether this is something Rails itself lacks, Bryce says that "might be a bridge too far." They could imagine it as a gem others might find useful. Overall, they say the pattern "has paid a lot of dividends" in building speed. The cost is the usual one for anything homegrown: teaching new people the pattern and keeping documentation up to date.

How action responders changed integration testing

Bryce says the most interesting benefit has been in testing. The team still uses FactoryBot. But they kept running into bugs where every test looked fine. Looking closer, the tests passed because they lived in "stub hell," a hyper-idealized state that didn't include the messy side-effect logic real requests trigger. To work around this, the team was writing custom seed files and fixtures to reproduce realistic states.

Action responders changed that. Because a seed file can call an action responder exactly the way a network request would, the setup code runs the same code path as production, with the same parameter parsing and the same side effects. Complex situations can be built as a sequence of calls, and Bryce says this lets them "guarantee I'm in this state of the world." In their view, it brings ordinary test setup much closer to real integration testing.

Observability with Datadog, and AI for reading stack traces

Datadog is the team's main APM. Aptible provides some tooling too, but most diagnosis happens in Datadog. Robby notes that the conversation was recorded in mid-May 2026 and asks about AI tooling. Bryce says they haven't used much of Datadog's own AI features. What they value, including during development, is using AI tools to read stack traces. They call this "almost the ideal case" for AI: the work is doable but tedious, since you'd otherwise trace line by line which code called which code to reach the problem. Having that done instantly frees them for higher-level thinking. They mention using trace deep-dives in particular.

100,000 SMS messages a day, and a fairness problem in Sidekiq

The main operational story is about background jobs. The team uses Sidekiq for all job processing. Users send on the order of 100,000 Twilio messages a day through the app, all as async jobs so they can be retried properly. Usage is bursty.

The queues were technically healthy. Bryce admits the team had given itself a fairly generous SLA, but it wasn't being breached. The problem was how the queue felt to users. Bryce's example: one user logs in and queues messages to, say, 500 patients. Another user then queues their own 500. The first user sees messages go out and replies start arriving. The second user waits for the first user's entire batch to finish, and Bryce notes the real numbers are much larger than 500.

The team made a few changes. The largest was restructuring batches. Previously, a batch job spawned many child jobs, and those had to wait for the batch to be picked up. Now the batch is enqueued immediately, with all of it on the same queue. Then they split the work so the first hundred or so messages of any batch, however large, go on a very fast queue. Everyone gets a quick response. If someone sends 10,000 messages, the 9,000th might not go out for an hour, but no one is stuck waiting because another user sent 10,000 first.

Robby asks whether Twilio's rate limits were part of the problem. Bryce says Twilio has a multi-tenant structure: each doctor's office gets its own dedicated sub-account. As far as they know, rate limits apply per sub-account, so Twilio limits haven't caused many issues. The bottleneck was on Phamily's side.

Robby also suggests that a nurse sending 500 messages may not want 500 replies all at once. Bryce says that was a big part of the team's thinking. For a while they had kept adding more workers and more worker capacity. Eventually they asked whether they were solving the right problem. Maybe it's better for messages to be naturally spread out, so users don't have to think about timing. As Bryce puts it, you know you want to message these people today; you don't need to message them at exactly 10:06 a.m.

Naming Sidekiq queues by SLA

Bryce says one blog post "totally changed our philosophy": a Judoscale article on planning Sidekiq queues. Before, the team had ad hoc queues like import, real_time, low, and default, with weights tuned by feel. Bryce guesses many teams will recognize that setup.

The article recommends naming queues by their SLA. Phamily now has queues for within 5 seconds, within 5 minutes, within 30 minutes, and within an hour. Bryce describes two benefits. First, Datadog can alert when a queue's SLA is being missed. That prompts a developer to investigate: are too many jobs going onto that queue, does it need more processing capacity, or does some job belong on a slower queue? Second, developers ask far fewer questions like "should this go on the import queue or the default queue?" The question answers itself: how fast does it need to finish, and what is the user expecting? Since the change, Bryce says, they rarely need to debug job processing. The earlier pain came from growth, since at low volumes nobody had thought much about queues.

Is Rails part of the secret sauce?

Bryce admits they're biased, and colleagues might say their own work matters more. Still, they believe Rails is a large part of Phamily's advantage. The company's vision includes using AI internally and also putting AI into care managers' daily workflows. The field moves fast, and at an AI company "you always feel like, oh no, someone's gonna vibe code my app in a week." Rails provides a reliable framework, so the team doesn't have to keep re-deciding how to build something technically sound or re-solving security basics. They can take those for granted and focus on what makes the product unique, which Bryce calls "almost the whole secret sauce."

Deploys, onboarding, and testing against real services

The team runs nightly builds and pushes to release branches, but not everything that's ready gets merged every night. Deploys run through GitHub Actions. Depending on which features are most urgent, Bryce estimates they ship about twice a week.

Onboarding and realistic test data are among the hardest problems, Bryce says. There is a staging environment that closely mirrors production and runs on the most secure infrastructure with all protections in place, but it's treated as a "break in case of emergency" resource. For day-to-day development and onboarding, the team relies heavily on seed data. Bryce thinks this is part of why the action-responder-based seeding matters so much: patient relationships last a long time, and a patient record with no history isn't useful for testing. Bryce says this is still an area they are working to improve. When a change carries risk that only real production data would reveal, the last round of testing and CI runs on the pre-production environment.

For SMS, the team mostly stubs and mocks external endpoints. It also uses Twilio's test phone numbers on a sandbox account; texting one of these numbers returns a realistic response, such as reporting that the number is a landline. Tests that touch this layer are grouped together. End-to-end tests use Playwright and usually run with external connections stubbed out. An environment variable flag can instead point them at a deployed environment connected to the Twilio sandbox.

A Claude Code pipeline that gives engineers "superpowers"

Robby says he doesn't want to turn the episode into an AI episode but asks how AI-assisted development fits into the team's work. Bryce says the team is just starting to move past everyone doing things their own way. What has settled is a phrase the founder likes: the team's job is "to give people superpowers." That used to describe the product. Now it also describes what the team wants for its own developers.

The main use case is smaller features: ideas that are good but sit in the backlog because nobody is pushing for them. The goal is to let an engineer take a vague idea or loose requirements and turn it into a feature product managers would approve. The team mostly uses Claude Code, with a set of skills that run a fairly rigid pipeline:

  1. From a description, or a ticket pasted in, generate a PRD. The engineer can take it to a PM and ask whether these look like the right requirements.
  2. Turn that into an FRD.
  3. Produce a technical design document that serves as the implementation plan.
  4. Finally, generate a test suite.

At each step, engineers are expected to read, refine, and critique the output the way a PM or any other reviewer would. The key rule is about tests. For tiny bug fixes, all this rigor isn't needed. But for features worth three or four days of effort, the developer must confirm that the tests make sense and are the right proof of the feature before the LLM writes any other code. Bryce says the results have been "very powerful." Engineers have built fairly complex features spanning front end and back end, with complicated requirements, largely on their own and with little involvement from people who would normally be pulled in.

Engineers and PMs moving toward each other

Robby notes that many companies he consults with are going the other way, giving product owners sandboxes in the codebase so they can experiment. Bryce says Phamily is doing both. In the past month, almost every PM got GitHub access and their own development environment. The strategy, though, is to keep PMs focused on larger feature- and epic-level work. That work requires gathering information from customers and, in Phamily's case, from regulations, and it's where PMs' time matters most. Smaller items, like a clunky UX flow or a slow page, turned out to suit both sides. Engineers were most bothered by these and kept asking why they couldn't just ship a fix for something they knew was broken. PMs kept asking why they were spending so much time on small improvements.

The Vue front end and the Rails back end are in separate repositories. A VS Code workspace lets Claude Code work across both in one pass. There is also a top-level wrapper repo that serves as a registry of the underlying repos, and that's where the skills live. Robby mentions that Brian Scanlan of Intercom, which Robby says had just renamed itself Fin, described tracking how skills are used. Bryce says someone else at the company may have looked into this, but the tooling is still early. It was only handed to engineers broadly in the last couple of months, after a few engineers trialed it and gave feedback.

A recent win: a business-process modeling engine

Bryce calls themselves not an AI skeptic but not an AI booster either. AI was fine for scripts and SQL queries, but they didn't want it writing application code: "That's my baby." That changed with a recent project, a fairly complicated business-process model. Bryce compares it to tools like Zapier, Flowise, or Lucidchart: a general way to represent flowcharts with decision points, which Bryce says are a big part of healthcare.

After a long evaluation, Bryce kept thinking they shouldn't build it themselves because others must have built it better. They didn't find what they needed. As a last resort, they wrote out exactly what they needed and what they liked and disliked about each option they'd evaluated, then asked Claude to help build something. Bryce calls the result "pretty impressive." The models were clean, and it wrote idiomatic Rails, which Bryce says they had felt was missing from AI tools for a long time. They shipped it much faster than they think they would have otherwise.

Programming as theory building

Robby asks whether someone without Bryce's domain knowledge could have gotten there, and whether Bryce worries about the developer's role. Bryce calls it "definitely an existential question right now" and answers with Peter Naur's essay "Programming as Theory Building," which they place in the 1980s or early '90s. Bryce summarizes Naur's argument this way: a programmer's output is not source code but the theory behind it. The code is a consequence of that theory, and this is also why adding more programmers doesn't make a project ship faster.

Bryce's view is that as producing code becomes more of a commodity, the tools free developers to do what they were always supposed to do: build the theory, think through what it involves, design the interfaces, and make the system effective, usable, and maintainable over time. This is why the team insists on tests in its AI pipeline. In Bryce's words, "the test suite almost becomes the theory of the code."

On why the company chose Rails, Bryce knows only part of the story. The founder, who was also a developer, had worked at Y Combinator companies and was in the startup scene when Rails appeared and took off. Having known him for ten years, Bryce says he "really embraces" the philosophy behind Rails.

Advice: build less, and learn C once

Asked for one decision other teams might benefit from, Bryce says it depends on each team's needs. The lesson they've learned repeatedly is not strictly technical: "you never need as much as you initially think." Programmers tend to think about edge cases and overbuild. Bryce's advice is to trust that what you actually need will emerge from what you ship. Robby connects this to the earlier discussion of dependencies: rely on the community for commodity pieces, and build your own where your domain is truly unusual, as with the business-process model.

Bryce doesn't have a favorite software book, since they came from math. They offer what they call a hotter take: everyone should force themselves to learn C at least once. They see programming history as layers of higher-level thinking, from assembly to manual memory management to expressive languages, and now, with AI, possibly less thinking about the language layer at all. Bryce says they've never written assembly and might feel the same way about it if they had. But getting down to C teaches how a computer thinks, and that builds intuition for debugging, for systems, and for what works and what doesn't. Robby wonders whether AI might make learning C faster and suggests it could be worth trying.

Jaan Health doesn't have an engineering blog. The company posts occasionally on LinkedIn, and its marketing site is phamily.com, "family" with a "Ph."