Languages, and Where They Run
Circuitry runs ten languages — Python, JavaScript, TypeScript, Ruby, Lua, Go, Rust, C, C++ and Zig — plus Shell when a computer of your own is connected. The choice is made per step, not per document, so one piece of work can wrangle data in Python, hand the heavy pass to Rust, and format the answer in JavaScript.
You pick it in the same place on both surfaces: the language name in a CodeBook cell's toolbar, or the language dropdown on a Code node on the canvas. Those are the same thing — see One document, two views below.
Where each language runs
| Language | Where it runs | What it needs |
|---|---|---|
| Python | On the device you're working on — phone, tablet or computer — or on a connected computer, which is much faster | Nothing |
| JavaScript, TypeScript | On the device you're working on | Nothing |
| Lua, Ruby | On the device you're working on | Nothing |
| Go, Rust, C, C++, Zig | On the device you're working on, once built | A computer of your own — to build it, once |
| Shell | On a computer of your own | A connected computer |
Compiled languages become WebAssembly
Go, Rust, C, C++ and Zig are not interpreted. The first time you run a step written in one — and again whenever you change its code — Circuitry compiles it to WebAssembly (WASM): a portable, sandboxed machine-code format that runs at close to native speed.
That compile step is what buys you three things at once:
- Speed. A compiled step runs as compiled code, not as a script being read line by line. This is the reason to reach for one — a numeric pass, a parser, an image transform.
- Portability. WebAssembly is not tied to the machine that built it. The built result runs on any device you open the document on afterwards, a phone included.
- Build once. The result is saved with your workflow, so the step runs instantly from then on — with nothing connected, and offline. You are only asked to build again when you actually change the code, and a workflow someone shares with you arrives already built.
Building needs a computer of your own
A compiler is a real program that has to live on a real machine, so this is the one part that cannot happen on the device in your hand:
- In the desktop app — Circuitry Studio for macOS, Windows or Linux — it just works. The first time you run a Go, Rust, C, C++ or Zig step, Circuitry offers to install that language's compiler on your computer, then builds with it. There is nothing to download in advance and nothing to configure; you don't need the language installed already.
- On iPhone, iPad, Android or the web, you need Circuit running on your computer and connected. Circuitry installs the compiler there and builds there, and the result comes back to the device you're holding and runs on it.
Building is part of the Personal plan and above, which is what the desktop app and Circuit come with. Running an already-built step needs neither a plan nor a connection. If a step reports its build tools are missing, run it again to retry.
So the practical rule on a phone or tablet: build once beside your computer, then run anywhere. Nothing about a compiled step ties the finished thing to a desk.
Python, JavaScript, TypeScript, Ruby and Lua need none of this
They run on whatever device you're working on, on every plan, with nothing installed and nothing connected — a phone included. There is no build step and no wait.
- Python also runs natively on a connected computer, which is dramatically faster for real work and is what you want for anything numeric. Choose where a step runs in its settings; a CodeBook picks it up from Show Config.
- TypeScript is written as you would write it in any file — interfaces, annotations, generics. The types come off the moment the step runs, so what executes is your code as JavaScript, in the same place and at the same speed.
- Ruby and Lua each have one place they run and no settings to choose, so there is no execution picker on either. Both come with their standard library and nothing beyond it.
Shell is the exception in the other direction: it always runs on a computer of your own, never on the device in your hand.
Ruby, specifically
Ruby is the full language and the whole standard library — json, set, digest, csv, time, date, securerandom, uri and the rest. Blocks, each, map, sum, string methods, Struct, Comparable: none of it is a subset. A step is a plain script, and it passes on whatever it evaluates to — the last expression, or an explicit return.
require "json"
items = input["items"] || []
puts "summed #{items.length} items"
{
"total" => items.sum { |i| i["amount"].to_f }.round(2),
"count" => items.length,
"currency" => env("CURRENCY") || "USD",
}
The interpreter is a large download — around seven megabytes — fetched once on a device and then kept, so the first Ruby step you run waits and shows its progress, and every one after that starts immediately. It stays available offline afterwards.
What a Ruby step does not have:
- No gems.
requirereaches the standard library and nothing else. There is nobundle install, no Gemfile. - No files, no network, no commands. A step reads its input and returns a value.
system, backticks,Process.spawnandexecall say so rather than quietly doing nothing. Use an HTTP step for requests, and a Shell step on a connected computer for commands. - Nothing is shared between steps. Each one gets its own interpreter, so reopening a class or defining a constant affects that step and nothing else.
- It runs on your device only. A Ruby step is not available in a deployed workflow, a webhook or a schedule, which run on Circuitry's servers with no device in the loop.
Two things to know when a value crosses between a Ruby step and the rest of a workflow:
- Input keys are strings, not symbols:
input["items"]. After a Code node, Sheet or Agent, the value it produced isinput["value"]— see Values in Every Language. - Symbols become plain text on the way out, as keys and as values. Hash order is kept exactly as you built it. Whole numbers above about nine quadrillion lose their last digits — return those as strings if they must be exact. Returning a block or an object that has no data form stops the step and tells you where it was, rather than passing on something meaningless.
Lua, specifically
Lua is small — the whole interpreter is about a quarter of a megabyte, fetched once and then kept — so it starts instantly and stays available offline. It is a good fit for a rule, a transformation, a bit of glue between two steps: the kind of code where waiting for anything to load would cost more than the work itself.
What it gives you is the language and its standard library — string, table, math, utf8, coroutines, and the clock (os.time, os.date, os.clock). What it does not give you:
- No packages. There is no package manager and
requirecannot load anything. What ships with Lua is what you have. - No files, no network, no commands. A Lua step reads its input and returns a value; it cannot open a file, make a request, or run a program. Use a Shell step on a connected computer for those.
- It runs on your device only. A Lua step is not available in a deployed workflow or a webhook run, which happen on Circuitry's servers with no device in the loop. A workflow that needs to run unattended should use Python or JavaScript for those steps.
Two things to know when a value crosses between a Lua step and the rest of a workflow, because Lua and the data format workflows pass around do not line up exactly:
- Lua tables are 1-indexed, so the first item of a list is
items[1]. - Lua has no null. A field that arrives empty is simply absent, so test it with
if input.thing ~= nil then. On the way back, a table counts as a list only when its keys are 1, 2, 3 … with no gaps; anything else — including an empty table — is passed on as a record.
The same step in two places is not always the same step
Python is the one language you can choose where to run, and that choice is real: a step can run on this device, through Circuit, or in cloud runs, and those are three different Pythons.
| Where | What it is | How you get there |
|---|---|---|
| On this device | the phone, tablet or computer you are working on | the default — nothing to connect, nothing to install |
| Through Circuit | a computer of your own, running the languages it has installed | connect Circuit and pick that computer in the step settings |
| In cloud runs | a deployed workflow, a webhook or a schedule, with no device in the loop | deploy the workflow, or give it a webhook or a schedule |
Most code does not notice. These are the differences that do, and each one is a boundary rather than a fault — closing them would mean either taking the numeric libraries off your device or putting a whole computer in every cloud run.
Numbers print differently on this device and through Circuit. Printing a single value out of a numeric array gives 1.5 on this device and np.float64(1.5) through Circuit. A step that builds a sentence or a filename out of array values produces different text in the two places, and nothing warns you — the step succeeds either way. The numerical library on this device is an older release than the one a recent computer will have installed, and it changed how a single value prints. Wrap the value in float(...) or str(round(x, 2)) before putting it in text; that reads the same everywhere.
Cloud runs have the standard library and nothing else. A step that imports a numerical or image library runs on this device and fails in a cloud run. Keep a step you intend to deploy to the standard library, or run that part through Circuit and pass the result in.
!pip install does not survive deployment. It installs a package on this device and through Circuit, and is a syntax error in a cloud run — it is not Python, it is a convenience the two interactive places translate for you, and a cloud run hands your code straight to Python. A step that uses it cannot be deployed.
The short version: if a workflow is going to run unattended, test it unattended. Run it once as a deployed workflow or through its webhook before you rely on it, rather than assuming the run you watched on your laptop is the run that will happen at three in the morning.
Ruby, Lua, JavaScript and TypeScript have none of this, because each has exactly one place it runs. A Ruby or Lua step means the same thing on a phone as on a desktop, with nothing to check — which is the other side of not being available in a deployed workflow at all.
One document, two views
A CodeBook is a workflow. Not an export of one, not a view generated from one — the same document, the same file, the same run. One engine executes both, so a cell and a node are the same step with the same input, output and settings.
Flip between them whenever you like: Workflow View on the notebook's bottom toolbar takes you to the canvas, and right-clicking a node there offers Open in CodeBook to come back.
Each view is better at a different thing:
- The notebook is linear and familiar. Top to bottom, prose between the steps, results under the cell that produced them. Best when the reasoning matters as much as the answer, and when you'll re-run and re-explain the work.
- The canvas is where a run stops being a straight line. Conditions and switches send it down different paths, loops repeat a section, fork and join split it, parallel processing runs branches at the same time, and with several machines connected each step can run on a different one.
Because it is one document, you don't have to choose once. Narrate an analysis in the notebook, flip to the canvas to fan the slow part out across a cluster of machines, then flip back to write up what it found. When it's finished it is also a workflow you can schedule, call as a webhook or deploy — no rewrite, because there was never a second thing to rewrite.
That is the part a notebook tied to a single language cannot reach: every cell picks its own language, compiled cells run at near-native speed on the device you happen to be holding, and the notebook is a runnable, branching, deployable workflow the whole time.
Related
- Code Node — writing a step in each language: the input/output contract, the standard library, and what a compiled step can't do.
- CodeBook Tour — every control in the notebook, cell by cell.
- Coming from Jupyter — how notebook concepts map across.
- Circuit — the companion app that gives Circuitry your computer.
- Parallel Processing and Several Machines — what the canvas view unlocks.
- Debugging — breakpoints in Python, JavaScript and TypeScript; printing in Ruby, Lua and the compiled five.