Posts in Ruby (20 found)
André Arko 3 days ago

Announcing <code>rv</code> 0.2

With the help of many new contributors, and after many late nights wrestling with make, we are happy to (slightly belatedly) announce the 0.2 release of rv ! This version dramatically expands support for Rubies, shells, and architectures. Rubies: we have added Ruby 3.3, as well as re-compiled all Ruby 3.3 and 3.4 versions with YJIT. On Linux, YJIT increases our glibc minimum version to 2.35 or higher. That means most distro releases from 2022 or later should work, but please let us know if you run into any problems. Shells: we have added support for bash, fish, and nushell in addition to zsh. Architectures: we have added Ruby compiled for macOS on x86, in addition to Apple Silicon, and added Ruby compiled for Linux on ARM, in addition to x86. Special thanks to newest member of the maintainers’ team @adamchalmers for improving code and tests, adding code coverage and fuzzing, heroic amounts of issue triage, and nushell support. Additional thanks are due to all the new contributors in version 0.2, including @Thomascountz , @lgarron , @coezbek , and @renatolond . To upgrade, run , or check the release notes for other options.

0 views
André Arko 5 days ago

The RubyGems “security incident”

Ruby Central posted an extremely concerning “ Incident Response Timeline ” today, in which they make a number of exaggerated or purely misleading claims. Here’s my effort to set the record straight. First, and most importantly: I was a primary operator of RubyGems.org, securely and successfully, for over ten years. Ruby Central does not accuse me of any harms or damages in their post, in fact stating “we have no evidence to indicate that any RubyGems.org data was copied or retained by unauthorized parties, including Mr. Arko.” The actions I took during a time of great confusion and uncertainty (created by Ruby Central!) were careful, specific, and aimed to defend both Ruby Central the organization and RubyGems.org the service from potential threats. The majority of the team, including developers in the middle of paid full-time work for Ruby Central, had just had all of their permissions on GitHub revoked. And then restored six days later. And then revoked again the next day. Even after the second mass-deletion of team permissions, Marty Haught sent an email to the team within minutes, at 12:47pm PDT, saying he was (direct quote) “terribly sorry” and “I messed up”. Update : Added email timestamp. The erratic and contradictory communication supplied by Marty Haught, and the complete silence from Shan and the board, made it impossible to tell exactly who had been authorized to take what actions. As this situation occurred, I was the primary on-call. My contractual, paid responsibility to Ruby Central was to defend the RubyGems.org service against potential threats. Marty’s final email clearly stated “I’ll follow up more on this and engage with the governance rfc in good faith.”. Just a few minutes after that email, at 1:01pm PDT, Marty also posted a public GitHub comment , where he agreed to participate in the proposed governance process and stated “I’m committed to find the right governance model that works for us all. More to come.” Update : screenshot of comment removed and replaced with link, since the comment appears to still be visible (at least to logged out users) on GitHub. Given Marty’s claims, the sudden permission deletions made no sense. Worried about the possibility of hacked accounts or some sort of social engineering, I took action as the primary on-call engineer to lock down the AWS account and prevent any actions by possible attackers. I did not change the email addresses on any accounts, leaving them all owned by a team-shared email at rubycentral.org, to ensure the organization retained overall control of the accounts, even if individuals were somehow taking unauthorized actions. Within a couple of days, Ruby Central made an (unsigned) public statement, and various board members agreed to talk directly to maintainers. At that point, I realized that what I thought might have been a malicious takeover was both legitimate and deliberate, and Marty would never “fix the permissions structure”, or “follow up more” as he said. Once I understood the situation, I backed off to let Ruby Central take care of their “security audit”. I left all accounts in a state where they could recover access. I did not alter, or try to alter, anything in the Ruby Central systems or GitHub repository after that. I was confident, at the time, that Ruby Central’s security experts would quickly remove all outside access. My confidence was sorely misplaced. Almost two weeks later, someone asked if I still had access and I discovered (to my great alarm), that Ruby Central’s “security audit” had failed. Ruby Central also had not removed me as an “owner” of the Ruby Central GitHub Organization. They also had not rotated any of the credentials shared across the operational team using the RubyGems 1Password account. I believe Ruby Central confused themselves into thinking the “Ruby Central” 1Password account was used by operators, and they did revoke my access there. However, that 1Password account was not used by the open source team of RubyGems.org service operators. Instead, we used the “RubyGems” 1Password account, which was full of operational credentials. Ruby Central did not remove me from the “RubyGems” 1Password account, even as of today. Aware that I needed to disclose this surprising access, but also aware that it was impossible for anyone except former operators to exploit this security failure, I immediately wrote an email to Ruby Central to disclose the problem. Here is a copy of my disclosure email, in full. Ruby Central did not reply to this email for over three days. When they finally did reply, they seem to have developed some sort of theory that I was interested in “access to PII”, which is entirely false. I have no interest in any PII, commercially or otherwise . As my private email published by Ruby Central demonstrates, my entire proposal was based solely on company-level information, with no information about individuals included in any way. Here’s their response, over three days later. In addition to ignoring the (huge) question of how Ruby Central failed to secure their AWS Root credentials for almost two weeks, and appearing to only be aware of it because I reported it to them , their reply also failed to ask whether any other shared credentials might still be valid. There were more. Unbeknownst to me, while I was answering Marty’s email in good faith, Ruby Central’s attorney was sending my lawyer a letter alleging I had committed a federal crime, on the theory that I had “hacked” Ruby Central’s AWS account. On the contrary, my actions were taken in defense of the service that Ruby Central was paying me to support and defend. With my side of the story told, I’ll leave it to you to decide whether you think it’s true that “Ruby Central remains committed to transparent, responsible stewardship of the RubyGems infrastructure and to maintaining the security and trust that the Ruby ecosystem depends on.”

1 views
マリウス 6 days ago

Alpine Linux on a Bare Metal Server

When I began work on 📨🚕 ( MSG.TAXI ) , I kept things deliberately low-key, since I didn’t want it turning into a playground for architecture astronauts . For the web console’s tech stack, I went with the most boring yet easy-to-master CRUD stack I could find , that doesn’t depend on JavaScript . And while deploying Rails in a sane way (without resorting to cOnTaInErS ) is a massive PITA, thanks to the Rails author’s cargo-cult mentality and his followers latching onto every half-baked wannabe-revolutionary idea, like Kamal and more recently Omarchy , as if it were a philosopher’s stone, from a development perspective it’s still the most effective getting-shit-done framework I’ve used to date. Best of all, it doesn’t rely on JavaScript (aside from actions, which can be avoided with a little extra effort). Similarly, on the infrastructure side, I wanted a foundation that was as lightweight as possible and wouldn’t get in my way. And while I’m absolutely the type of person who would run a Gentoo server, I ultimately went with Alpine Linux due to its easier installation, relatively sane defaults (with a few exceptions, more on that later ), and its preference for straightforward, no-nonsense tooling that doesn’t try to hide magic behind the scenes. “Why not NixOS?” you might ask. Since I’m deploying a lightweight, home-brewed Ruby/Rails setup alongside a few other components, I didn’t see the point of wrapping everything as Nix packages just to gain the theoretical benefits of NixOS. In particular, the CI would have taken significantly longer, while the actual payoff in my case would have been negligible. Since I’m paying for 📨🚕 out of my own pocket, I wanted infrastructure that’s cheap yet reliable. With plenty of people on the internet praising Hetzner , I ended up renting AMD hardware in one of their Finnish datacenters. Hetzner doesn’t offer as many Linux deployment options as cloud providers like Vultr , so I had to set up Alpine myself, which was pretty straightforward. To kickstart an Alpine installation on a Hetzner system, you just need to access the server’s iPXE console, either by renting a Hetzner KVM for an hour or by using their free vKVM feature. From there, you can launch the Alpine Linux by initializing the network interface and chain-loading the file: From that point on setup should be easy thanks to Alpine’s installer routine. If you’re using Hetzner’s vKVM feature to install Alpine, this chapter is for you. Otherwise, feel free to skip ahead. vKVM is a somewhat hacky yet ingenious trick Hetzner came up with, and it deserves a round of applause. If you’re curious about how it works under the hood, rent a real KVM once and reboot your server into vKVM mode. What you’ll see is that after enabling vKVM in Hetzner’s Robot , iPXE loads a network image, which boots a custom Linux OS. Within that OS, Hetzner launches a QEMU VM that uses your server’s drives to boot whatever you have installed. It’s basically Inception at the OS level. As long as vKVM is active (meaning the iPXE image stays loaded), your server is actually running inside this virtualized environment, with display output forwarded to your browser. Run while in vKVM mode and you’ll see, for example, your NIC showing up as a VirtIO device. Here’s the catch: When you install Alpine through this virtualized KVM environment, it won’t generate the your physical server actually needs. For instance, if your server uses an NVMe drive, you may discover that doesn’t include the module, causing the OS to fail on boot. Hetzner’s documentation doesn’t mention this, and it can easily bite you later. Tl;dr: If you installed your system via vKVM , make sure your includes all necessary modules. After updating , regenerate the . There are several ways to do this, but I prefer . Always double-check that the regenerated really contains everything you need. Unfortunately Alpine doesn’t provide tools for this, so here’s a .tar.gz with Debian’s and . Extract it into , and note that you may need to for them to work properly, due to Alpine’s somewhat annoying defaults (more on that later ). Finally, after rebooting, make sure you’ve actually left the vKVM session. You can double check by running . If the session is still active (default: 1h), your system may have just booted back into the VM, which you can identify by its Virt-devices. As soon as your Alpine Linux system is up and running there are a couple of things that I found important to change right off the bat. Alpine’s default boot timeout is just 1 second, set in ( ). If you ever need to debug a boot-related issue over a high-latency KVM connection, you will dread that 1-second window. I recommend increasing it to 5 seconds and running to apply the change. In practice, you hopefully won’t be rebooting the server that often, so the extra four seconds won’t matter day-to-day. Alpine uses the classic to configure network settings. On Hetzner’s dedicated servers, you can either continue using DHCP for IPv4 or set the assigned IP address statically. For IPv6, you’ll be given a subnet from which you can choose your own address. Keep in mind that the first usable IPv6 on Hetzner’s dedicated servers is : Amongst the first things you do should be disabling root login and password authentication via SSH: Apart from that you might want to limit the type of key exchange methods and algorithms that your SSH server allows, depending on the type of keys that you’re using. Security by obscurity: Move your SSH server from its default port (22) to something higher up and more random to make it harder for port-scanners to hit it. Finicky but more secure: Implement port knocking and use a handy client to open the SSH port for you only, for a limited time only. Secure: Set up a small cloud instance to act as Wireguard peer and configure your server’s SSH port to only accept connections from the cloud instance using a firewall rule . Use Tailscale if a dedicated Wireguard instance is beyond your expertise. You will likely want to have proper (GNU) tools around, over the defaults that Alpine comes with ( see below ). Some of the obvious choices include the following: In addition, I also like to keep a handful of convenience tools around: This is a tricky part because everyone’s monitoring setup looks different. However, there are a few things that make sense in general. Regardless what you do with your logs it’s generally a good idea to switch from BusyBox to something that allows for more advanced configurations, like syslog-ng : You probably should have an overview of how your hardware is doing. Depending on what type of hard drives your server has, you might want to install the or packages. UFW is generally considered an uncomplicated way to implement firewalling without having to complete a CCNP Security certification beforehand: Depending on your SSH setup and whether you are running any other services that could benefit from it, installing Fail2Ban might make sense: The configuration files are located at and you should normally only create/edit the files. The easiest way to backup all the changes that you’ve made to the general configuration is by using , the integrated Alpine local backup solution that was originally intended as a tool to manage diskless mode installations. I would, however, recommend to manually back up installed packages ( ) and use Restic for the rest of the system, including configuration files and important data, e.g.: However, backups depend on the data that your system produces and your desired backup target. If you’re looking for an easy to use, hosted but not-too-expensive one-off option, then Tarsnap might be for you. You should as well look into topics like local mail delivery, system integrity checks (e.g. AIDE ) and intrusion detection/prevention (e.g. CrowdSec ). Also, if you would like to get notified for various server events, check 📨🚕 ( MSG.TAXI ) ! :-) One of the biggest annoyances with Alpine is BusyBox : You need SSH? That’s BusyBox. The logs? Yeah, BusyBox. Mail? That’s BusyBox, too. You want to untar an archive? BusyBox. What? It’s gzipped? Guess what, you son of a gun, gzip is also BusyBox. I understand why Alpine chose BusyBox for pretty much everything, given the context that Alpine is most often used in ( cOnTaInErS ). Unfortunately, most BusyBox implementations are incomplete or incompatible with their full GNU counterparts, leaving you wondering why something that worked flawlessly on your desktop Linux fails on the Alpine box. By the time I finished setting up the server, there was barely any BusyBox tooling left. However, I occasionally had to resort to some odd trickery to get things working. You now have a good basis to set up whatever it is that you’re planning to use the machine for. Have fun! Footnote: The artwork was generated using AI and further botched by me using the greatest image manipulation program .

0 views
ptrchm 1 weeks ago

Event-driven Modular Monolith

The main Rails app I currently work on has just turned eight. It’s not a huge app. It doesn’t deal with web-scale traffic or large volumes of data. Only six people working on it now. But eight years of pushing new code adds up. This is a quick overview of some of the strategies we use to keep the codebase maintainable. After the first few years, our codebase suffered from typical ailments: tight coupling between domains, complex database queries spread across various parts of the app, overgrown models, a maze of side effects triggered by ActiveRecord callbacks , endlessly chained associations (e.g. ) – with an all-encompassing model sitting on top of the pile. Modular Monolith Pub/Sub (Events) Patterns Service Objects Repositories for Database Queries Slim and Dumb Models Bonus: A Separate Frontend App How Do I Start?

0 views
André Arko 1 weeks ago

Announcing gem.coop, a community gem server

The team behind the last ten years of rubygems.org, including @deivid-rodriguez, @duckinator, @martinemde, @segiddins, @simi, and myself, is very pleased to announce a new gem server for the Ruby community: gem.coop . The new server’s governance policies are being prepared in coordination with Mike McQuaid of Homebrew, and will be released later this week. The current versions of RubyGems and Bundler work with this new server already, and any Ruby developer is welcome to switch to using this new server immediately. We have exciting plans to add new features and functionality in the coming days. Join us!

1 views
Robin Moffatt 1 weeks ago

Stumbling into AI: Part 5—Agents

A short series of notes for myself as I learn more about the AI ecosystem as of Autumn [Fall] 2025. The driver for all this is understanding more about Apache Flink’s Flink Agents project, and Confluent’s Streaming Agents . I started off this series —somewhat randomly, with hindsight—looking at Model Context Protocol ( MCP ) . It’s a helper technology to make things easier to use and provide a richer experience. Next I tried to wrap my head around Models —mostly LLMs, but also with an addendum discussing other types of model too. Along the lines of MCP, Retrieval Augmented Generation ( RAG ) is another helper technology that on its own doesn’t do anything but combined with an LLM gives it added smarts. I took a brief moment in part 4 to try and build a clearer understanding of the difference between ML and AI . So whilst RAG and MCP combined make for a bunch of nice capabilities beyond models such as LLMs alone, what I’m really circling around here is what we can do when we combine all these things: Agents ! But…what is an Agent, both conceptually and in practice? Let’s try and figure it out. Let’s begin with Wikipedia’s definition : In computer science, a software agent is a computer program that acts for a user or another program in a relationship of agency . We can get more specialised if we look at Wikipedia’s entry for an Intelligent Agent : In artificial intelligence, an intelligent agent is an entity that perceives its environment, takes actions autonomously to achieve goals , and may improve its performance through machine learning or by acquiring knowledge . Citing Wikipedia is perhaps the laziest ever blog author’s trick, but I offer no apologies 😜. Behind all the noise and fuss, this is what we’re talking about: a bit of software that’s going to go and do something for you (or your company) autonomously . LangChain have their own definition of an Agent, explicitly identifying the use of an LLM: An AI agent is a system that uses an LLM to decide the control flow of an application. The blog post from LangChain as a whole gives more useful grounding in this area and is worth a read. In fact, if you want to really get into it, the LangChain Academy is free and the Introduction to LangGraph course gives a really good primer on Agents and more. Meanwhile, the Anthropic team have a chat about their definition of an Agent . In a blog post Anthropic differentiates between Workflows (that use LLMs) and Agents: Workflows are systems where LLMs and tools are orchestrated through predefined code paths. Agents, on the other hand, are systems where LLMs dynamically direct their own processes and tool usage, maintaining control over how they accomplish tasks. Independent researcher Simon Willison also uses the LLM word in his definition: An LLM agent runs tools in a loop to achieve a goal. He explores the definition in a recent blog post: I think “agent” may finally have a widely enough agreed upon definition to be useful jargon now , in which Josh Bickett’s meme demonstrates how much of a journey this definition has been on: That there’s still discussion and ambiguity nearly two years after this meme was created is telling. My colleague Sean Falconer knows a lot more this than I do. He was a guest on a recent podcast episode in which he spells things out: [Agentic AI] involves AI systems that can reason, dynamically choose tasks, gather information, and perform actions as a more complete software system. [ 1 ] [Agents] are software that can dynamically decide its own control flow: choosing tasks, workflows, and gathering context as needed. Realistically, current enterprise agents have limited agency[…]. They’re mostly workflow automations rather than fully autonomous systems . [ 2 ] In many ways […] an agent [is] just a microservice . [ 3 ] A straightforward software Agent might do something like: Order more biscuits when there are only two left The pseudo-code looks like this: We take this code, stick it on a server and leave it to run. One happy Agent, done. An AI Agent could look more like this: Other examples of AI Agents include: Coding Agents . Everyone’s favourite tool (when used right). It can reason about code, it can write code, it can review PRs. One of the trends that I’ve noticed recently (October 2025) is the use of Agents to help with some of the up-front jobs in software engineering (such as data modelling and writing tests ), rather than full-blown code that’s going to ship to production. That’s not to say that coding Agents aren’t being used for that, but by using AI to accelerate certain tasks whilst retaining human oversight (a.k.a. HITL ) it makes it easier to review the output rather than just trusting to luck that reams and reams of code are correct. There’s a good talk from Uber on how they’re using AI in the development process, including code conversion, and testing. Travel booking . Perhaps you tell it when you want to go, the kind of vacation you like, and what your budget is; it then goes and finds where it’s nice at that time of year, figures out travel plans within your budget, and either proposes an itinerary or even books it for you. Another variation could be you tell it where , and then it integrates with your calendar to figure out the when . This is a canonical example that is oft-cited; I’d be interested if anyone can point me to an actual implementation of it, even if just a toy one . I saw this in a blog post from Simon Willison that made me wince, but am leaving the above in anyway just to serve as an example of the confusion/hype that exists in this space: comes from plus , the latter meaning of, relating to, or characterised by . So is simply AI that is characterised by an Agent, or Agency. Contrast that to AI that’s you sat at the ChatGPT prompt asking it to draw pictures of a duck dressed as a clown . Nothing Agentic about that—just a human-led and human-driven interaction. "AI Agents" becomes a bit of a mouthful with the qualifier, so much of the current industry noise is simply around "Agents". That said, "Agentic AI" sounds cool, so gets used as the marketing term in place of "AI" alone. So we’ve muddled our way through to some kind of understanding of what an Agent is, and what we mean by Agentic AI. But how do we actually build one? All we need is an LLM (such as access to the API for OpenAI or Claude ), something to call that API (there are worse choices than !), and a way to call external services (e.g. MCP servers) if the LLM determines that it needs to use them. So in theory we could build an Agent with some lines of bash, some API calls, and a bunch of sticky-backed plastic . This is a grossly oversimplified example (and is missing elements such as memory)—but it hopefully illustrates what we’re building at the core of an Agent. On top of this goes all the general software engineering requirements of any system that gets built (suitable programming language and framework, error handling, LLM output validation, guard rails, observability, tests, etc etc). The other nuance that I’ve noticed is that whilst the above simplistic diagram is 100% driven by an LLM (it decides what tools to call, it decides when to iterate) there are plenty of cases where an Agent is to some degree rules-driven. So perhaps the LLM does some of the autonomous work, but then there’s a bunch of good ol' statements in there too. This is also borne out by the notion of "Workflows" when people talk about Agents. An Agent doesn’t wake up in the morning and set out on its day serving only to fulfill its own goals and enrichment. More often than not an Agent is going to be tightly bound into a pre-defined path with a limited range of autonomy. What if you want to actually build this kind of thing for real? That’s where tools like LangGraph and LangChain come in. Here’s a notebook with an example of an actual Agent built with these tools. LlamaIndex is another framework, with details of building an Agent in their docs. As we build up from the so-simple-it-is-laughable strawman example of an Agent above, one of the features we’ll soon encounter is the concept of memory. The difference between a crappy response and a holy-shit-that’s-magic response from an LLM is often down to context . The richer the context, the better a chance it has at generating a more accurate output. So if an Agent can look back on what it did previously, determining what worked well and what didn’t, perhaps even taking into account human feedback, it can then generate a more successful response the next time. You can read a lot more about memory in this chapter of Agentic Design Patterns by Antonio Gulli . This blog post from "The BIG DATA guy" is also useful: Agentic AI, Agent Memory, & Context Engineering This diagram from Generative Agents: Interactive Simulacra of Human Behavior (J.S. Park, J.C. O’Brien, C.J. Cai, M.R. Morris, P. Liang, M.S. Bernstein) gives a good overview of a much richer definition of an Agent’s implementation. The additional concepts include memory (discussed briefly above), planning, and reflection: Also check out Paul Iusztin’s talk from QCon London 2025 on The Data Backbone of LLM Systems . Around the 35-minute mark he goes into some depth around Agent architectures. Just as you can build computer systems as monoliths (everything done in one place) or microservices (multiple programs, each responsible for a discrete operation or domain), you can also have one big Agent trying to do everything (probably not such a good idea) or individual Agents each good at their particular thing that are then hooked together into what’s known as a Multi-Agent System (MAS). Sean Falconer’s family meal planning demo is a good example of a MAS. One Agent plans the kids' meals, one the adults' meals, another combines the two into a single plan, and so on. This is a term you’ll come across referring to the fact that Agents might be pretty good, but they’re not infallible. In the travel booking example above, do we really trust the Agent to book the best holiday for us? Almost certainly we’d want—at a minimum—the option to sign off on the booking before it goes ahead and sinks £10k on an all-inclusive trip to Bognor Regis. Then again, we’re probably happy enough for an Agent to access our calendars without asking permission, and as to whether they need permission or not to create a meeting is up to us and how much we trust them. When it comes to coding, having an Agent write code, test it, fix the broken tests, compare it to a spec, and iterate is really neat. On the other hand, letting it decide to run …less so 😅. Every time an Agent requires HITL, it reduces its autonomy and/or responsiveness to situations. As well as simply using smarter models that make fewer mistakes, there are other things that an Agent can do to reduce the need for HITL such as using guardrails to define acceptable parameters. For example, an Agent is allowed to book travel but only up to a defined threshold. That way the user gets to trade off convenience (no HITL) with risk (unintended first-class flight to Hawaii). 📃 Generative Agents: Interactive Simulacra of Human Behavior 🎥 Paul Iusztin - The Data Backbone of LLM Systems - QCon London 2025 📖 Antonio Gulli - Agentic Design Patterns 📖 Sean Falconer - https://seanfalconer.medium.com/ Coding Agents . Everyone’s favourite tool (when used right). It can reason about code, it can write code, it can review PRs. One of the trends that I’ve noticed recently (October 2025) is the use of Agents to help with some of the up-front jobs in software engineering (such as data modelling and writing tests ), rather than full-blown code that’s going to ship to production. That’s not to say that coding Agents aren’t being used for that, but by using AI to accelerate certain tasks whilst retaining human oversight (a.k.a. HITL ) it makes it easier to review the output rather than just trusting to luck that reams and reams of code are correct. There’s a good talk from Uber on how they’re using AI in the development process, including code conversion, and testing. Travel booking . Perhaps you tell it when you want to go, the kind of vacation you like, and what your budget is; it then goes and finds where it’s nice at that time of year, figures out travel plans within your budget, and either proposes an itinerary or even books it for you. Another variation could be you tell it where , and then it integrates with your calendar to figure out the when . This is a canonical example that is oft-cited; I’d be interested if anyone can point me to an actual implementation of it, even if just a toy one . I saw this in a blog post from Simon Willison that made me wince, but am leaving the above in anyway just to serve as an example of the confusion/hype that exists in this space: 📃 Generative Agents: Interactive Simulacra of Human Behavior 🎥 Paul Iusztin - The Data Backbone of LLM Systems - QCon London 2025 📖 Antonio Gulli - Agentic Design Patterns 📖 Sean Falconer - https://seanfalconer.medium.com/

0 views
Joel Drapper 2 weeks ago

Ruby Central’s “security measures” leave front door wide open

Despite locking maintainers out of

1 views
Joel Drapper 2 weeks ago

The RubyGems takeover from the perspective of an open source developer

We must demand that Ruby Central make this right.

0 views
Joel Drapper 2 weeks ago

Yippee: Our vision for modern full-stack Ruby framework

What would it look like to build a new web application framework from a blank sheet of paper today?

15 views
André Arko 2 weeks ago

stupid jj tricks

This post was originally given as a talk for JJ Con . The slides are also available. Welcome to “stupid jj tricks”. Today, I’ll be taking you on a tour through many different jj configurations that I have collected while scouring the internet. Some of what I’ll show is original research or construction created by me personally, but a lot of these things are sourced from blog post, gists, GitHub issues, Reddit posts, Discord messages, and more. To kick things off, let me introduce myself. My name is André Arko, and I’m probably best known for spending the last 15 years maintaining the Ruby language dependency manager, Bundler. In the world, though, my claim to fame is completely different: Steve Klabnik once lived in my apartment for about a year, so I’m definitely an authority on everything about . Thanks in advance for putting into the official tutorial that whatever I say here is now authoritative and how things should be done by everyone using , Steve. The first jj tricks that I’d like to quickly cover are some of the most basic, just to make sure that we’re all on the same page before we move on to more complicated stuff. To start with, did you know that you can globally configure jj to change your name and email based on a path prefix? You don’t have to remember to set your work email separately in each work repo anymore. I also highly recommend trying out multiple options for formatting your diffs, so you can find the one that is most helpful to you. A very popular diff formatter is , which provides syntax aware diffs for many languages. I personally use , and the configuration to format diffs with delta looks like this: Another very impactful configuration is which tool jj uses to handle interactive diff editing, such as in the or commands. While the default terminal UI is pretty good, make sure to also try out Meld, an open source GUI. In addition to changing the diff editor, you can also change the merge editor, which is the program that is used to resolve conflicts. Meld can again be a good option, as well as any of several other merging tools. Tools like mergiraf provide a way to attempt syntax-aware automated conflict resolution before handing off any remaining conflicts to a human to resolve. That approach can dramatically reduce the amount of time you spend manually handling conflicts. You might even want to try FileMerge, the macOS developer tools built-in merge tool. It supports both interactive diff editing and conflict resolution. Just two more configurations before we move on to templates. First, the default subcommand, which controls what gets run if you just type and hit return. The default is to run , but my own personal obsessive twitch is to run constantly, and so I have changed my default subcommand to , like so: The last significant configuration is the default revset used by . Depending on your work patterns, the multi-page history of commits in your current repo might not be helpful to you. In that case, you can change the default revset shown by the log command to one that’s more helpful. My own default revset shows only one change from my origin. If I want to see more than the newest change from my origin I use to get the longer log, using the original default revset. I’ll show that off later. Okay, enough of plain configuration. Now let’s talk about templates! Templates make it possible to do many, many things with jj that were not originally planned or built in, and I think that’s beautiful. First, if you haven’t tried this yet, please do yourself a favor and go try every builtin jj template style for the command. You can list them all with , and you can try them each out with . If you find a builtin log style that you especially like, maybe you should set it as your default template style and skip the rest of this section. For the rest of you sickos, let’s see some more options. The first thing that I want to show you all is the draft commit description. When you run , this is the template that gets generated and sent to your editor for you to complete. Since I am the kind of person who always sets git commit to verbose mode, I wanted to keep being able to see the diff of what I was committing in my editor when using jj. Here’s what that looks like: If you’re not already familiar with the jj template functions, this uses to combine strings, to choose the first value that isn’t empty, to add before+after if the middle isn’t empty, and to make sure the diff status is fully aligned. With this template, you get a preview of the diff you are committing directly inside your editor, underneath the commit message you are writing. Now let’s look at the overridable subtemplates. The default templates are made of many repeated pieces, including IDs, timestamps, ascii art symbols to show the commit graph visually, and more. Each of those pieces can be overrides, giving you custom formats without having to change the default template that you use. For example, if you are a UTC sicko, you can change all timestamps to render in UTC like , with this configuration: Or alternatively, you can force all timestamps to print out in full, like (which is similar to the default, but includes the time zone) by returning just the timestamp itself: And finally you can set all timestamps to show a “relative” distance, like , rather than a direct timestamp: Another interesting example of a template fragment is supplied by on GitHub, who changes the node icon specifically to show which commits might be pushed on the next command. This override of the template returns a hollow diamond if the change meets some pushable criteria, and otherwise returns the , which is the regular icon. It’s not a fragment, but I once spent a good two hours trying to figure out how to get a template to render just a commit message body, without the “title” line at the top. Searching through all of the built-in jj templates finally revealed the secret to me, which is a template function named . With that knowledge, it becomes possible to write a template that returns only the body of a commit message: We first extract the title line, remove that from the front, and then trim any whitespace from the start of the string, leaving just the description body. Finally, I’d like to briefly look at the possibility of machine-readable templates. Attempting to produce JSON from a jj template string can be somewhat fraught, since it’s hard to tell if there are quotes or newlines inside any particular value that would need to be escaped for a JSON object to be valid when it is printed. Fortunately, about 6 months ago, jj merged an function, which makes it possible to generate valid JSON with a little bit of template trickery. For example, we could create a output of a JSON stream document including one JSON object per commit, with a template like this one: This template produces valid JSON that can then be read and processed by other tools, looks like this. Templates have vast possibilities that have not yet been touched on, and I encourage you to investigate and experiment yourself. Now let’s look at some revsets. The biggest source of revset aliases that I have seen online is from @thoughtpolice’s jjconfig gist, but I will consolidate across several different config files here to demonstrate some options. The first group of revsets roughly corresponds to “who made it”, and composes well with other revsets in the future. For example, it’s common to see a type alias, and a type alias to let the current user easily identify any commits that they were either author or committer on, even if they used multiple different email addresses. Another group uses description prefixes to identify commits that have some property, like WIP or “private”. It’s then possible to use these in other revsets to exclude these commits, or even to configure jj to refuse to push them. Thoughtpolice seems to have invented the idea of a , which is a group of commits on top of some parent: Building on top of the stack, it’s possible to construct a set of commits that are “open”, meaning any stack reachable from the current commit or other commits authored by the user. By setting the stack value to 1, nothing from trunk or other remote commits is included, so every open commit is mutable, and could be changed or pushed. Finally, building on top of the open revset, it’s possible to define a “ready” revset that is every open change that isn’t a child of wip or private change: It’s also possible to create a revset of “interesting” commits by using the opposite kind of logic, as in this chain of revsets composed by . You take remote commits and tags, then subtract those from our own commits, and then show anything that is either local-only, tracking the remote, or close to the current commit. Now let’s talk about jj commands. You probably think I mean creating jj commands by writing our own aliases, but I don’t! That’s the next section. This section is about the jj commands that it took me weeks or months to realize existed, and understand how powerful they are. First up: . When I first read about absorb, I thought it was the exact inverse of squash, allowing you to choose a diff that you would bring into the current commit rather than eject out of the current commit. That is wildly wrong, and so I want to make sure that no one else falls victim to this misconception. The absorb command iterates over every diff in the current commit, finds the previous commit that changed those lines, and squashes just that section of the diff back to that commit. So if you make changes in four places, impacting four previous commits, you can to squash all four sections back into all four commits with no further input whatsoever. Then, . If you’re taking advantage of jj’s amazing ability to not need branches, and just making commits and squashing bits around as needed until you have each diff combined into one change per thing you need to submit… you can break out the entire chain of separate changes into one commit on top of trunk for each one by just running and letting jj do all the work for you. Last command, and most recent one: . You can use fix to run a linter or formatter on every commit in your history before you push, making sure both that you won’t have any failures and that you won’t have any conflicts if you try to reorder any of the commits later. To configure the fix command, add a tool and a glob in your config file, like this: Now you can just and know that all of your commits are possible to reorder without causing linter fix conflicts. It’s great. Okay. Now we can talk about command aliases. First up, the venerable . In the simplest possible form, it takes the closest bookmark, and moves that bookmark to , the parent of the current commit. What if you want it to be smarter, though? It could find the closest bookmark, and then move it to the closest pushable commit, whether that commit was , or , or . For that, you can create a revset for , and then tug from the closest bookmark to the closest pushable, like this: Now your bookmark jumps up to the change that you can actually push, by excluding immutable, empty, or descriptionless commits. What if you wanted to allow tug to take arguments, for those times when two bookmarks are on the same change, or when you actually want to tug a different bookmark than the closest one? That’s also pretty easy, by adding a second variant of the tug command that takes an argument: This version of tug works just like the previous one if no argument is given. But if you do pass an argument, it will move the bookmark with the name that you passed instead of the closest one. How about if you’ve just pushed to GitHub, and you want to create a pull request from that pushed bookmark? The command isn’t smart enough to figure that out automatically, but you can tell it which bookmark to use: Just grab the list of bookmarks attached to the closest bookmark, take the first one, pass it to , and you’re all set. What if you just want single commands that let you work against a git remote, with defaults tuned for automatic tugging, pushing, and tracking? I’ve also got you covered. Use to colocate jj into this git repo, and then track any branches from upstream, like you would get from a git clone. Then, you can to find the closest bookmark to , do a git fetch, rebase your current local commits on top of whatever just got pulled, and then show your new stack. When you’re done, just . This push handles looking for a huggable bookmark, tugging it, doing a git push, and making sure that you’re tracking the origin copy of whatever you just pushed, in case you created a new branch. Last, but definitely most stupid, I want to show off a few combo tricks that manage to deliver some things I think are genuinely useful, but in a sort of cursed way. First, we have counting commits. In git, you can pass an option to log that simply returns a number rather than a log output. Since jj doesn’t have anything like that, I was forced to build my own when I wanted my shell prompt to show how many commits beyond trunk I had committed locally. In the end, I landed on a template consisting of a single character per commit, which I then counted with . That’s the best anyone on GitHub could come up with, too . See? I warned you it was stupid. Next, via on Discord, I present: except for the closest three commits it also shows at the same time. Simply create a new template that copies the regular log template, while inserting a single conditional line that adds if the current commit is inside your new revset that covers the newest 3 commits. Easy. And now you know how to create the alias I promised to explain earlier. Last, but definitely most stupid, I have ported my previous melding of and over to , as the subcommand , which I alias to because it’s inspired by , the shell cd fuzzy matcher with the command . This means you can to see a list of local bookmarks, or to see a list of all bookmarks including remote branches. Then, you can to do a fuzzy match on , and execute . Jump to work on top of any named commit trivially by typing a few characters from its name. I would love to also talk about all the stupid shell prompt tricks that I was forced to develop while setting up a zsh prompt that includes lots of useful jj information without slowing down prompt rendering, but I’m already out of time. Instead, I will refer you to my blog post about a jj prompt for powerlevel10k , and you can spend another 30 minutes going down that rabbit hole whenever you want. Finally, I want to thank some people. Most of all, I want to thank everyone who has worked on creating jj, because it is so good. I also want to thank everyone who has posted their configurations online, inspiring this talk. All the people whose names I was able to find in my notes include @martinvonz, @thoughtpolice, @pksunkara, @scott2000, @avamsi, @simonmichael, and @sunshowers. If I missed you, I am very sorry, and I am still very grateful that you posted your configuration. Last, I need to thank @steveklabnik and @endsofthreads for being jj-pilled enough that I finally tried it out and ended up here as a result. Thank you so much, to all of you.

2 views
DHH 2 weeks ago

We've all had enough of this nonsense

Every few years, the same sad contingent of Ruby malcontents tries to cancel me from Rails. At the peak of the woke era, back in 2022, they were actually successful in getting Ruby Central to uninvite me from doing the yearly keynote at RailsConf. But now RailsConf is dead, Rails World is thriving, and the cancellation nonsense is over. Only I guess nobody told that same sad contingent! Because three days ago, they tried yet again, with the same trite grab bag of accusations: "he holds racist and transphobic views, as well as a number of other traits undesirable". And to add to the outrage theater, they named their little letter after a French resistance action fighting the Nazis during WWII. Subtle! Except this time, nobody cared. In fact, quite the opposite. Thousands of people have taken to X and elsewhere to reject this nonsense, and that's apparently making one of the organizers very sad: So far though, my experience has been that there are many more negative responses than positive. Maybe the Ruby community isn’t the place I thought it was, and MINASWAN was always a lie. That makes me sad. 😢 I guess I would be sad too if I had named my group after THE GOOD GUYS and then it turned out that everyone thought I was THE BAD GUYS. But that's exactly what happened. The outpouring of support from all sides has been overwhelming. This is what it looks like when preference falsification finally falls. When normal people are no longer afraid to say no to these people. Then it's revealed just how small and isolated these aggrieved individuals actually are. Tobi from Shopify said it best: It’s such a terrible mental tax on builders that divisive clowns just ride in and spew these bullshit terms that they clearly don’t understand themselves in bad faith. Ignore & keep building. That's exactly what we're going to do. We're going to reject and ignore these nut jobs. Then we're going to keep building.

0 views
André Arko 2 weeks ago

Bundler belongs to the Ruby community

I’ve spent 15 years of my life working on Bundler. When I introduce myself, people say “oh, the Bundler guy?”, and I am forced to agree. I didn’t come up with the original idea for Bundler (that was Yehuda). I also didn’t work on the first six months worth of prototypes. That was all Carl and Yehuda together, back when “Carlhuda” was a super-prolific author of Ruby libraries, including most of the work to modularize Rails for version 3. I joined the team at a pivotal moment, in February 2010, as the 0.9 prototype was starting to be re-written yet another time into the shape that would finally be released as 1.0. By the time Carl, Yehuda, and I released version 1.0 together in August 2010, we had fully established the structure and commands that Bundler 2.7.2 still uses today. I gave my first conference talk about Bundler at Red Dirt Ruby in May 2010. Because they would be too busy with Rails 3 talks, Yehuda and Carl asked me to give the first RailsConf talk about Bundler, in June 2010. As Carl and Yehuda drifted off to other projects, in 2011 and 2012 respectively, I took on a larger role, co-maintaining the project with Terence Lee, then on the Ruby platform team at Heroku. We shipped (and, embarrassingly, broke) many versions of Bundler on our way to the 1.1 release and its major speed improvements. We also gave several conference talks together, sharing what we had learned about Bundler, about gems, and about maintaining open source. In 2013, I managed to convince the owner of to sell me his domain, and rebuilt the website to host a separate copy of the documentation for every version of Bundler, ensuring even users on old versions could still access accurate documentation. By the end of 2013, Terence had drifted away from the project as well, and I realized that everyone using Ruby was now one bus (or one lottery ticket) away from Bundler having no significant maintainers. During 2014, I made sure to settle any remaining ownership issues, including purchasing the rights to the Bundler logo, and began investigating various funding ideas. I tried specialized consulting, corporate sponsorships, and asking Ruby Central about sponsoring Bundler and RubyGems development work. Ruby Central declined, citing their desire to stay focused on conferences, but suggested that if I wanted to pursue something myself they would be happy to collaborate. In 2015, I founded Ruby Together specifically to raise funds to pay the existing maintainers team of Bundler, RubyGems, and RubyGems.org. Over time, we were able to raise enough money to quietly but scrappily keep the entire RubyGems ecosystem maintained and functional. Ruby Together did not ever, at any point, demand any form of governance or control over the existing open source projects. Maintainers did their thing in the RubyGems and Bundler GitHub orgs, while Ruby Together staff and board members did their thing in the rubytogether GitHub org. By 2021, when Ruby Central and Ruby Together were both interested in merging together, funds were harder to find. Ruby Together had a membership program. Ruby Central wanted a to have a membership program. The confusing split between “Ruby Central owns the AWS account, but Ruby Together pays all the devs” continued to be a problem. We prepared a merger agreement (which you can read in full at the link), stating that Ruby Central’s new goal after the merger would be “paying maintainers to do the programming”. The agreement also states that Ruby Central will follow Ruby Together’s Vision, Mission, and Values , a document that is hosted in the rubycentral GitHub organization today. That document includes a very specific list of goals, including: You can read much more in both the merger agreement and in the Mission, Vision, and Values document, but the fundamental goal for both the non-profit and the open source projects is clear: this is all for the Ruby community. Without the community, there is no point to this work, and there is no way it could ever have been done in the first place. Without the 354 individuals who contributed to Bundler and to RubyGems, I could never have become “the Bundler guy” in the first place. In the last few weeks, Ruby Central has suddenly asserted that they alone own Bundler. That simply isn’t true. In order to defend the reputation of the team of maintainers who have given so much time and energy to the project, I have registered my existing trademark on the Bundler project. Trademarks do not affect copyright, which stays with the original contributors unchanged. Trademarks do not affect license terms, which stay MIT and unchanged. Trademarks only impact one thing: who is allowed say that what they make is named “Bundler”. Ruby Central is welcome to the code, just like everyone else. They are not welcome to the project name that the Bundler maintainers have painstakingly created over the last 15 years. While the trademark has been registered under my name as an individual, I will not keep it for myself, because the idea of Bundler belongs to the Ruby community. Once there is a Ruby organization that is accountable to the maintainers, and accountable to the community, with openly and democratically elected board members, I commit to transfer my trademark to that organization. I will not license the trademark, and will instead transfer ownership entirely. Bundler should belong to the community, and I want to make sure that is true for as long as Bundler exists.

0 views
Joel Drapper 3 weeks ago

Shopify, pulling strings at Ruby Central, forces Bundler and RubyGems takeover

Ruby Central recently took over a collection of open source projects from their maintainers without their consent.

0 views
tekin.co.uk 3 weeks ago

The Ruby community has a DHH problem

David Celis recently published a thoughtful piece on Rails governance in response to the latest troubling blog post from DHH, the creator of Rails. Like David, I’ve also been troubled by DHH’s recent output and the harm it is causing to the Ruby community. I think it’s worth taking a moment to analyse DHH’s post in more detail and make it clear exactly why it’s so problematic. In his post, DHH complains that London is no longer a city he wants to live in because it is now only a third “native Brit”. His use of “native Brit” is as a proxy for “White British”. The implication is clear: if you are not White, you are not British. In the same post he praises Tommy Robinson (actual name Stephen Christopher Yaxley-Lennon), a right-wing agitator with several convictions for violent offences and a long history of association with far-right groups such as the English Defence League and the British Nationalist Party . He then goes on to describe those that attended last weekend’s far-right rally in London as “perfectly normal, peaceful Brits” protesting against the “demographic nightmare” that has enveloped London, despite the violence and disorder they caused . To all of that he ads a dash of Islamophobia, citing “Pakistani rape gangs” as one of the reasons for the unrest, repeating a weaponised trope borne from a long since discredited report from the Quilliam Foundation, an organisation with ties to both the the US Tea Party , and Tommy Robinson himself. A trope that exists despite the fact that the overwhelming majority of convicted child sex offenders are white men , with Asian men in fact under-represented.

0 views
André Arko 3 weeks ago

Goodbye, RubyGems

As chronicled by my teammate Ellen, the RubyGems team is no more . I wish the best of luck to everyone taking on the herculean task of keeping package management functional and working for the entire Ruby community. In the meantime, I’m looking forward to spending my new free time focusing on projects that I’m truly excited about, like . We’d love to have your help as we work to build next-generation tools for Ruby.

0 views
Phil Eaton 1 months ago

In response to a developer asking about systems

Sometimes I get asked questions that would be more fun to answer in public. All letters are treated as anonymous unless permission is otherwise granted. Hey [Redacted]! It's great to hear from you. I'm very glad you joined the coffee club and met some good folks. :) You asked how to learn about systems. A great question! I think I need to start first with what I mean when I say systems. My definition of systems is all of the underlying software we developers use but are taught not to think about because they are so solid: our compilers and interpreters, our databases, our operating system, our browser, and so on. We think of them as basically not having bugs, we just count on them to be correct and fast enough so we can build the applications that really matter to users. But 1) some developers do actually have to work on these fundamental blocks (compilers, databases, operating systems, browsers, etc.) and 2) it's not thaaaat hard to get into this development professionally and 3) even if you don't get into it professionally, having a better understanding of these fundamental blocks will make you a better application developer. At least I think so. To get into systems I think it starts by you just questioning how each layer you build on works. Try building that layer yourself. For example you've probably used a web framework like Rails or Next.js. But you can just go and write that layer yourself too (for education). And you've probably used Postgres or SQLite or DynamoDB. But you can also just go and write that layer yourself (for education). It's this habit of thinking and digging into the next lower layer that will get you into systems. Basically, not being satisfied with the black box. I do not think there are many good books on programming in general, and very very few must-read ones, but one that I recommend to everybody is Designing Data Intensive Applications. I think it's best if you read it with a group of people. (My book club will read it in December when the 2nd edition comes out, you should join.) But this book is specific to data obviously and not interested in the fundamentals of other systems things like compilers or operating systems or browsers or so on. Also, I see getting into this as a long-term thing. Throughout my whole career (almost 11 years now) I definitely always tried to dig into compilers and interpreters, I wrote and blogged about toy implementations a lot. And then 5 years ago I started digging into databases and saw that there was more career potential there. But it still took 4 years until I got my first job as a developer working on a database (the job I currently have). Things take time to learn and that's ok! You have a long career to look forward to. And if you end up not wanting to dig into this stuff that's totally fine too. I think very few developers actually do. And they still have fine careers. Anyway, I hope this is at least mildly useful. I hope you join the Software Internals Discord and nycsystems.xyz as well and look forward to seeing you at future coffee clubs! Cheers, Phil I wrote a letter in response to a developer asking about how to learn systems. pic.twitter.com/2ILNpzl662

0 views
iDiallo 1 months ago

Why AI should be a form, not a conversation

Maybe it was a decade ago when it seemed like every single game developer started believing that all games should be open world. Infinite possibilities, player freedom, emergent storytelling, who wouldn't want that? But open world design introduced new problems that linear games never faced. Players would get lost in meaningless side quests while the main story waited forgotten. Developers would pile on features that didn't serve the core experience. Worst of all, players often couldn't tell when they'd actually "won." I sank an unhealthy amount of time into Metal Gear Solid V when it came out, wandering its open world long after I'd completed every meaningful objective, never quite sure if I was done or just... tired. The problem with open world design is scope creep disguised as feature richness. When everything is possible, nothing feels special. Today, with this new wave of AI, we're making the exact same mistake. We're taking the "open world" approach to problem-solving, and the results are just as messy. When the chatbot craze was at its peak, I worked for a startup where we automated customer service. Many of our competitors were building what I now recognize as "open world chatbots". AI systems with infinite conversational possibilities. These chatbots would greet you, ask about your day, make jokes, and try to make you forget that you had a very specific reason for starting the session. Mind you, this was before ChatGPT, when LLMs weren't widely available. Each competitor's demo showcased the chatbot's vast capabilities through highly choreographed scenarios. Like game developers showing off sprawling landscapes and complex skill trees, they were proud of the sheer scope of what their AI could theoretically handle. The problem was identical to open world games: the moment real users engaged with these systems, everything collapsed. Customers, like players, are unpredictable. They don't follow the golden path you've designed. Our approach was fundamentally different. We built what I'd call a "linear gameplay" AI. Our chatbot didn't try to converse with you. It didn't greet you, didn't chat about the weather, didn't pretend to be your friend. It appeared as a form or email address, a clear single point of entry with one mission: solve your customer service problem. When you sent your fully formed message, our application took over with purposeful, sequential steps. It read the message, classified it, analyzed sentiment, then moved through our integrations (Zendesk, Shopify, Magento, USPS) like a player progressing through carefully designed levels. When it retrieved your tracking information or order details, it crafted a response that directly answered your questions. If at any point it couldn't complete the mission, it executed a clean handoff to a human agent who received a comprehensive summary. Like a save file with all the relevant progress data. This agent was designed for one specific quest: resolve customer service problems . Nothing else. Just like how the best linear games focus relentlessly on their core mechanics rather than trying to be everything to everyone. A lot of our "open world" competitors have either pivoted or gone out of business since then. Which makes it even more surprising to watch Taco Bell's drive-thru AI failing in exactly the same way. Just a few weeks back, Taco Bell went through a PR nightmare when a customer ordered 18,000 cups of water, and the system dutifully added every single cup to the order. This wasn't malicious hacking; this was a classic open world design failure. The system was built to handle "anything," so it handled everything, including completely absurd requests that broke the entire experience. Taco Bell uses a service called Omilia to power their AI drive-thru. On their website, they explicitly describe their open world approach: Taco Bell recognized the importance of providing a seamless, human-like experience for customers, so Omilia's Voice AI Solution was meticulously tuned to provide clear, accurate responses, delivering a conversational experience that mirrors human interaction and improves the customer's ordering experience. Notice the language: "human-like experience," "conversational," "mirrors human interaction." They built an open world when they needed linear gameplay. The conversational nature invites exactly the kind of scope creep that breaks systems. Regular customers report their perfectly normal orders failing spectacularly, with the AI getting stuck in loops, asking "Would you like a drink with that?" even after drinks were clearly specified. I couldn't find a Taco Bell with AI drive-thru in my neighborhood, but I did find Rally's. They use a different company called HI Auto , and to my surprise, it worked flawlessly. The experience felt like a well-designed level progression: clear prompts, sequential steps, defined objectives. "What would you like to drink?" "What size?" Next level unlocked. It wasn't a conversation, it was a voice-powered form. No philosophical debates, no jokes, no attempts at charm. Just a transaction with guided rails and clear success criteria. The software knew exactly what it was supposed to accomplish, and users knew exactly what was expected of them. This is the difference between open world chaos and linear focus. A drive-thru isn't a space for exploration and emergent dialogue. It's a hyper-linear experience where success is defined as "correct order, minimal time." Any system that invites deviation from this core mission is poorly designed for its context. You could theoretically plug ChatGPT into a drive-thru experience, just like you could theoretically add an open world to any game. But you'd be guaranteed to receive unexpected inputs that break the core experience. Instead, treat AI applications like carefully designed HTML forms with proper validation, clear fields, and predictable outcomes. The goal is always the solution (a correct order, a resolved support ticket, a completed task), not the conversational medium. In fact, I think voice itself is not the most optimal for these linear experiences. The most efficient drive-thru model might be a QR code at the menu board that lets you complete the entire "form" on your phone before reaching the speaker. Choose linear when story and progression matter, open world when exploration serves the core purpose. The best AI applications should match their interaction model to their mission. Maybe OpenAI's goal is to build an AI. But your drive thru AI is perfectly fine being specialized. Start building AI systems that do one thing exceptionally well, with clear boundaries, defined success criteria, and linear paths to completion. Your users will thank you, and you won't end up in viral videos featuring 18,000 cups of water.

0 views
(think) 1 months ago

Why I Chose Ruby over Python

This year I spent a bit of time playing with Python, after having mostly ignored it since 2005 when was learning it originally. I did like Python back then, but a few years afterwards I discovered Ruby and quickly focused my entire attention on it. There were many (mostly small) reasons why I leaned towards Ruby back then and playing with Python now made me remember a few of them. I thought it might be interesting to write a bit about those, so here we go. Disclaimer: This is not a rant and I know that: So, treat this as an amusing personal account and nothing more than that. Probably the thing about Python that bothered me the most what stuff that would normally be methods are global functions (that often call some object methods internally). I’m referring to the likes of: You can find the full list here . I’m guessing the reason for this (as usual) is historical, but I much prefer the way Ruby does things. E.g. instead of or vs . is a pretty weird beast as it can: Why would someone want a keyword for removing items from lists and dictionaries instead of some method is beyond me. Ruby’s arrays have methods like: In Ruby almost everything is an expression (meaning that evaluating it would result in a value). In Python a lot of things are consider “statements” - something executed for their side effects only. If you haven’t used languages like Ruby or Lisp this might sound a bit strange, but if we go back to the previous section about , we can observe that: That’s something that I really value and I consider it one of the bigger practical advantages of Ruby over Python. At first I thought the semantic indentation used by Python is super cool, as it reduces a bit the typing one needs to do: In hindsight, however, I quickly realized that it also: One more thing - 4 spaces by default seems a tad too much to me, although that’s obviously debatable. P.S. I feel obliged to admit I’m not a big fan of either and would have preferred instead, but it is how it is. I’m not a fan of Python’s type as for me it’s a pretty weird duck: I get how things ended up the way they are, but for me it’s not OK to be able to write code like . I’m also not a fan of treating empty collection literals as , although I definitely have less issues with this than with 0 and 1. To compare this with Ruby: This definitely resonates better with me. Side note: Lately I’ve been playing a lot with languages like OCaml, F# and Rust, that’s why to me it now feels extra strange to have a boolean type that works like an integer type. I really like the range literals in Ruby: Python has the function that kind of gets the job done, but for whatever reason it doesn’t have the option to mark something as inclusive range. Definitely not a big deal, but one of the many small touches of Ruby’s syntax that I came to appreciate over time. In Python one has to pass to instance methods explicitly, which always seemed to me like an excessive level of verbosity. Also, that’s quite uncommon in other object-oriented programming languages. Many special methods have names surrounded with , which I find both odd and not very easy to type. I get why this was chosen (to avoid naming conflicts), but I don’t like it regardless. I really like that in Ruby the return value of a method is the value of the last expression that got evaluated in the method. There’s a expression in Ruby, but it’s rarely needed in practice. In Python, by comparison, you always have to use , otherwise your method will return . Not a big deal in general, but as I spend a lot of time with Ruby and various functional programming languages, it’s definitely something that bothers me. Admittedly that’s a very small one, but I would have preferred if anonymous functions were created with a keyword like or instead of . In Ruby historically they were created with as well, but afterwards the shorthand was introduced as well. There’s nothing wrong with the Python syntax per se, but I think that in general for lambdas it’s better to have a more compact syntax. Ruby predicates typically have names ending in - e.g. , , . This makes them really easy to spot while reading some code. Python sticks to the more common convention of prefixing such methods with , , etc and that’s fine. One thing that bothers me a bit is that often there’s not spaces between the prefix and the rest of the name (e.g. ), which doesn’t read great in my opinion. More importantly, in Ruby and Python it’s common to have destructive and non-destructive versions of some methods. E.g. - vs in Ruby, and vs in Python. I don’t know about you, but to me it seems that: I’m guessing the reasons here are also historical. Multi-line text literals are common in many languages, but I’m not super fond of: Who thought that typing those would be easy? It’s not that HEREDOCS in Ruby are great either, but I guess they are at least more common in various programming languages. Ruby has and . That’s it. Everyone uses them. Life is simple. Things are a lot more complicated in the realm of Python where several different tools have been in fashion over the years: Now it seems that might replace them all. Until something replaces I guess… And that’s a wrap. I’m guessing at this point most Rubyists reading this would probably agree with my perspective (shocking, right?) and most Pythonistas won’t. And that’s fine. I’m not trying to convince anyone that Ruby’s a better language than Python, I’m just sharing the story of how I ended up in team Ruby almost 20 years ago. Back in the day I felt that Ruby’s syntax was more elegant and more consistent than Python’s, and today my sentiment is more or less the same. Don’t get me wrong, though - I like Python overall and enjoy using it occasionally. It just doesn’t make me as happy as Ruby does. I’ve long written about my frustrations with Ruby, so it feels pretty good to write for once about the aspects of Ruby that I really enjoy. Keep hacking! P.S. After writing this I realized I had already written a similar article 14 years ago, but I had totally forgotten about it! Oh, well… the things I prefer in Ruby over Python are super subjective for every thing that Ruby does “better” there’s something else that Python does better delete variables dictionary items (to remove some values) (to remove some index) There’s no obvious way to determine if actually removed something In Ruby, however, most deletions result in informative results makes things harder for tooling, as it can’t really rearrange indented code sections in editors you can’t have much in terms of auto-indenting as you type (as the editor can’t know when a block finishes without you outdenting explicitly) was added only in Python 2.3 it inherits from and are essentially 1 and 0 has nothing to do with numbers there the only things are considered logically false are and (inclusive range) (exclusive range) Ruby encourages you to favor the non-destructive versions of the methods, unlike Python Ruby’s more consistent than Python

0 views