skip to content

The Name Collision That Found Two Tools


I built a CLI to dispatch and manage AI coding tasks. I named it ribosome — the same name as the bash script that executes those tasks. The collision was not caught in design. It was caught when I tried to replace the bash script with the new CLI and realised the live Temporal worker calls ribosome with provider and turn-limit flags to execute work. Replacing the binary would break every in-flight workflow. This was not a naming problem. It was a design problem wearing a naming hat.

The bash script takes a prompt and produces code changes. It configures environment variables, points the Claude Code harness at a Chinese LLM provider’s Anthropic-compatible endpoint, and invokes it. It is the executor. The CLI dispatches tasks to a Temporal workflow engine, queries status, cancels workflows, runs health checks. It is the controller. Same name, completely different verbs. The collision forced a question I should have asked at design time: who are the callers? The executor is called by the Temporal worker on a remote machine. It has live callers. Renaming it means updating the worker. The controller is called by me via the AI agent. It had zero callers — I just built it. The rename cost is asymmetric. The thing with more callers is harder to rename. The thing with zero callers is free to rename.

The executor is a ribosome — in cell biology, the ribosome is the molecular machine that translates mRNA into protein. My bash script translates prompts into code. The name fits the mechanism. The controller is not a ribosome. It decides whether translation happens, how much, and monitors the results. In cell biology, the master regulator of translation is mTOR — a kinase that controls ribosome activity based on nutrient signals and growth factors. So the controller became mtor. The executor kept ribosome. The pattern generalises: kubectl manages Kubernetes but is not itself a kubelet. flyctl manages Fly machines but is not a machine. docker CLI manages containers but is not containerd. The dispatcher gets the role-name. The executor keeps the natural name.

At design time, before naming anything, ask: does a binary with this name already exist in the tool chain? If yes, you have two callers competing for one name. The binary with more live callers keeps the name. The new thing gets named for its role.

There is a subtler version of this collision worth noting. When I built the Python CLI, my instinct was to rewrite the bash executor in Python too. But the bash script’s entire job is to set environment variables and invoke a binary. That is what shell scripts exist for. A Python rewrite would wrap subprocess.run and os.environ around the same operation — more code for the same result, with added virtual environment startup overhead inside every Temporal workflow. Match the tool to the job shape. If the job is configure env and call a binary, bash is native. If the job is parse structured input, manage state, emit JSON, Python is native. Two tools with different shapes doing different jobs. Two things that should not share a name, and also should not share an implementation language. The collision kept teaching.