skip to content
Terry Li

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 wasn’t caught in design. It was caught when I tried to replace the bash script with the new CLI and realized: the live Temporal worker calls ribosome --provider zhipu --max-turns 25 "task" to execute work. Replacing the binary would break every in-flight workflow.

This wasn’t a naming problem. It was a design problem wearing a naming hat.

Two tools, one name

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’s the executor.

The CLI dispatches tasks to a Temporal workflow engine, queries status, cancels workflows, runs health checks. It’s 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 translocase.py on the Temporal worker. It has live callers. Renaming it means updating the worker on a remote machine.
  • 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.

Name the role, not the substrate

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 generalizes: kubectl manages Kubernetes but isn’t itself a kubelet. flyctl manages Fly machines but isn’t a machine. docker CLI manages containers but isn’t containerd. The dispatcher gets the role-name; the executor keeps the natural name.

The check that prevents this

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.

This is the same discipline as checking for port conflicts before deploying a service. The namespace is shared; check it before you write to it.

The bash question

There’s a subtler version of this collision that’s worth noting. When I built the Python CLI, my instinct was to rewrite the bash executor in Python too. “It’s 400 lines of bash with embedded Python — surely a Click CLI would be cleaner.”

But the bash script’s entire job is: set environment variables, invoke a binary. That’s 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 venv 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. Once again: two things that shouldn’t share a name, and also shouldn’t share an implementation language.

The collision kept teaching.