This is Episode 1 of The Vibe Coder’s Guide to the Machine: computer science for a new generation.
Say you want to know when a pair of headphones goes on sale. You describe it to an AI in two sentences: check this product page every ten minutes, and send me a Telegram message if the price drops. A minute later you have a short file called price_bot.py. You run it in a terminal, it prints Watching..., and you go and make dinner.
On Thursday the price drops and your phone buzzes three times. Three identical messages, a few seconds apart.
You open the file, or ask the AI to, and the logic is fine. Fetch the price, compare it with the last one, send one message if it is lower, sleep, repeat. Nothing in the file says “three”. Then you remember the week. On Monday you ran it. On Tuesday you glanced at the tab and found the same single line from Monday night, the cursor blinking beneath it, not one word added in a whole day. A bot that says nothing looks exactly like a process that has stalled, so you opened a new tab and ran it again. On Wednesday you asked the AI why the bot was so quiet, and it ran the bot once more from its own terminal, saw that it worked, told you so, and the conversation ended there. You wrote one program. You started three processes, and all three are still running, each one checking the page, each one quite sure it is the only one.
That small mess rests on an idea older than any tool you used to build the bot: a program is a description, and running it makes a process. The process has a present moment, a place in its work where it is right now. It has a memory, values it is holding that the file does not contain. And it has a reach into the world: a network connection, a file on disk, your phone. The program is what you asked for. The process is what you are responsible for.
AI changed which of the two is hard to come by. A program now costs a sentence and a minute, and you can have one without reading it. The processes it becomes are no cheaper than they ever were. They run on your laptop or your cloud account, under your keys, sending messages in your name, and the only party that knows exactly how many you started is the operating system, which has kept count all week and seen no reason to mention it.
A program and the process it becomes
The file holds the program as text on a disk, doing nothing. You can copy it, email it or print it, and none of that checks a single price. A recipe is the usual comparison, and it holds this far: a recipe in a drawer feeds nobody.
When you type python3 price_bot.py and press Enter, you ask the operating system (macOS, Windows or Linux, the program that manages the machine on behalf of every process) to make something new. It sets aside a region of memory for a new process, gives it a number called a process ID, records what it is allowed to touch, and starts it going. A process is a program in execution together with everything the system keeps for it while it runs: its memory, its open files and connections, and its position in the work.
This is not hair-splitting, because the counts differ. One program can become any number of processes, and every run makes another. Your bot was one file and three processes, and the three had nothing to do with each other beyond having been started from the same text. Open Activity Monitor on a Mac, or Task Manager on Windows, and you will find them listed side by side.
You will also find that none of them is called price_bot. They are listed as Python, because the program each of them is running is python3. The bot’s program, the code in price_bot.py, is only what python3 was given to read.
Who actually reads your code
The file has to be read by python3, because the processor cannot read it: it does not read Python. It carries out instructions, small fixed operations built into the chip: copy a number, add two, compare them, jump to another instruction. A modern processor runs billions of them a second, and every process comes down to long sequences of them. Each family of chips has its own set, which is why the same application often comes in separate versions for different processors.
Your file is not made of those instructions; python3 is. It is the Python interpreter, a program written mostly in C and translated ahead of time into the processor’s instructions, and it reads price_bot.py, translates it into bytecode, a compact set of instructions for an imaginary machine of Python’s own, and then runs a loop: take the next bytecode instruction, carry it out, move to the next. Your code, whether you read it or not, is carried out this way, one step at a time.
The interpreter and everything it brings along while it carries out your code is called the runtime. It is the part that finds memory for a value when your code needs one and reclaims it when nothing uses the value any more, the standard library your bot leans on to fetch a web page, and the rules for what happens when an error goes unhandled. JavaScript has the same arrangement with Node or a browser. Code in languages such as Go or Rust is translated into the processor’s instructions ahead of time, so no interpreter is involved, but the file that results still carries a runtime of some size. Between what you wrote and what the chip does there is always software doing work on your behalf.
So running a program means something precise. Execution is a process stepping through instructions one after another, keeping track of where it is. Each such path through the instructions is called a thread, and one process can have several, each at its own place.
Where it is right now
Keeping track of where it is turns out to be the heart of the matter. The processor holds the address of the next instruction in a dedicated register, called the program counter or instruction pointer, and the Python interpreter keeps a marker of its own for the next bytecode. A program has no “now”: the file shows every line at once, the first and the last with equal standing. Each thread of a running process is always at one particular place in its work.
The people who first had to name this were trying to share a computer. In the mid-1960s at MIT, Jack Dennis and Earl Van Horn were working on machines that many users ran programs on at the same time, and their paper “Programming Semantics for Multiprogrammed Computations”, published in 1966, needed a word for what moves through a program as it is carried out. They chose “process”, and defined it in one line: “A process is a locus of control within an instruction sequence.” A locus of control is a present moment, the point the work has reached.
There is a twist an engineer may enjoy. In their scheme, several processes could work within one shared set of protected resources, which is close to what we now call threads inside a single process. Over the following decades the word moved. “Process” came to name the container, the memory and resources wrapped around the work, and “thread” took over the moving part. Both ideas were there at the start; only the labels shifted.
One memory or two
Each of your bot’s processes has threads too, because the AI made a decision you did not ask for. While writing it, the AI added a small extra: send the bot /price and it replies with the latest price. To listen for your message while the main loop sleeps, it started a second thread. A thread is a second present moment inside the same process: another path of execution, at its own place in the instructions, sharing all of that process’s memory.
Here is the part of the code that matters:
last_price = None
while True:
price = fetch_price(URL)
if last_price is not None and price < last_price:
send_message(f"Price dropped to {price}")
last_price = price
time.sleep(600)When the main loop stores a new value in last_price, the listener thread sees it at once, because there is only one last_price in that process and both threads are reading the same memory. Nothing is copied or sent between them. That is what makes threads convenient.
Your three processes are the opposite case. Each has its own memory, so each has its own last_price, and the operating system keeps a boundary between them: one process cannot simply read or write another’s memory. Crossing that boundary takes a deliberate arrangement the system allows, such as a region of memory both processes agree to share, or a debugger, a tool for inspecting a process, given permission to look inside. Your three processes had no such arrangement. Each of the three saw the price fall and none of them could know the others had seen it too. Three memories, three decisions, three messages. Each of them, by its own lights, had done its job perfectly.
The boundary is there on purpose. If one process crashes or scribbles over its own memory, the others are untouched; that protection is a large part of why operating systems bother with processes at all. Sharing memory, as threads do, buys convenience at a price: two threads can change the same value at the same moment and leave it in a state neither of them intended, a problem with enough depth to fill a good deal of computing.
Stop a running process
After Thursday you ask the AI for a guard against second copies. It adds a few lines that run just after the listener thread starts: the bot creates a file called price_bot.lock, and refuses to carry on if that file already exists. Then it tests the guard in a scratch folder, with copies of the bot that have it, separate from the week’s three processes, which never ran the guard. It starts one copy, which creates the lock file, then a second, and reports back that the second copy was refused with FileExistsError: [Errno 17] File exists: 'price_bot.lock', so the guard works.
Out of the habit the week taught you, you look at the process listing, in Activity Monitor or Task Manager or with ps in a terminal. The error is real, and the second copy did fail at once, but its prompt never came back, and two seconds after the failure the listing shows two Python processes with the same command. One of them is the refused copy, still running. When a Python program’s main line of work ends, as it did here with the error, Python checks whether any helper thread it must wait for is still running, and if one is, it keeps the process alive until that thread finishes. The listener had started before the error and never finishes, so the refused copy stays. A helper thread can be marked as one the program does not wait for, and then it is simply dropped at exit; had the listener been marked that way, the failed copy would have exited at once and only the first would remain.
You end the scratch copies with Ctrl-C or kill, and nothing from the scratch folder is left running.
The refused copy outlived its own error because of a thread, and stopping a process is where the difference between threads and processes decides what actually ends. So it helps to know the ordinary ways to stop a program you started in a terminal. The normal one is Ctrl-C, pressed in the terminal where it runs. It is a polite request to finish, and usually the program does and the prompt, the line where you type your next command, comes back. Closing the tab or window ends it too, because the terminal, as it closes, tells the program that nobody is there any more, and by default the program then ends. In a terminal, typekill followed by the process ID. That also asks the process to stop.
A stop request like this, sent by the operating system to a process, is called a signal. A plain kill sends SIGTERM, which the program may handle, for example by tidying up before it exits, or may ignore.kill -9 is different. It sends SIGKILL, which the program cannot catch, ignore, or handle. The operating system terminates the process without giving it a chance to run any cleanup code. Anything it was in the middle of may therefore be left behind, depending on what else owns or manages those resources.
End a process with kill, and every thread inside it ends with it: the loop, the listener, all of it. Python gives you no general way to kill a single thread from the outside; a thread normally ends when its own code finishes or when its process ends. Stop one of your three processes, on the other hand, and the other two carry on checking the page as if nothing had happened, because as far as they can tell, nothing has. Whether “stop it” stops everything depends on whether “it” is a thread or a process.
On Friday, you decide the bot has done its job. In Monday’s tab, the one in front of you, you press Ctrl-C. Python delivers the interrupt to the main thread, the loop stops, and a short traceback ending in KeyboardInterrupt appears. But the process may still be alive. Just as with the failed copy, the listener thread is still running, and if it is a non-daemon thread, Python will wait for it before exiting.
You press Ctrl-C again. Depending on exactly where Python is in its shutdown sequence, that may interrupt the wait and let the process finally exit. Another short traceback may appear, and the prompt returns.
You find Tuesday’s tab and close it. On Saturday, the price drops again and your phone buzzes once: one alert instead of three, exactly what you wanted all along. That message comes from Wednesday’s process, the one the AI started from its own terminal, in no tab you have ever seen. The only place to find it is in a process list or Activity Monitor, where it sits under the name Python, still quite sure it is the only one.
A process that starts other processes
There is one more kind of process in this story, and you may not know it exists. The shop builds its price with JavaScript after the page loads, so fetching the page’s raw text was not enough, and the AI reached for Playwright, a library that drives a real web browser with no window. Playwright does not run the browser inside your process. It starts a driver of its own, a Node process, and the driver starts Chromium as a separate process, which the library talks to over a connection. A two-sentence request about headphones is now running a web browser that has no window and no one to look at it.
That browser is a child process: a process started by another process, which becomes its parent. A child is not a thread. It has its own memory, its own process ID and its own lifetime, and the parent cannot reach into its memory any more than your three processes could reach into each other’s. They usually cooperate by passing messages across the boundary.
When the bot shuts down normally, Playwright closes the browser on its way out. Kill the bot outright with kill -9 and it gets no chance to close anything, yet within a third of a second the driver, Chromium and its helpers are gone as well, though the bot never got to ask them to go. What held them is a connection. The driver is tied to the bot by one, and Chromium is tied to the driver by a pipe, a live connection between the two; kill the driver alone and the browser vanishes just as fast: its parent ended, and it went too. A child with no such tie behaves differently. Have Python start a second Python process that only sleeps, kill the parent the same way, and the child keeps running. The system hands it to process 1, its first process, which on a Mac is launchd, and launchd keeps it, sleeping on with nobody left to answer to. So the useful question about any child is not who started it, but whether anything ties its life to its parent.
Here the life of a process starts to show through. Each process is started, runs and ends on its own terms. What a process held, the system takes back when it exits; what it started may outlive it. So when you stop a process, ask what it started, and whether that will stop too.
What the process holds while the clock runs
Look at what one of your three processes is holding at any moment. The current value of last_price. Its connection to Telegram. Its place in the loop, which might be halfway through a ten-minute sleep. The browser it started. All of that is state: everything that exists right now that the next step depends on. The file says last_price = None. The process says 249.00. Only one of them knows what the price was ten minutes ago.
Memory is where the process keeps its state while it runs: the region of the computer’s working memory the operating system handed it at the start and lets it grow. When the process exits, the system takes that memory back and the state goes with it. Restart the bot and the new process begins with last_price set to None; everything the old one knew is gone, quietly and completely, which is also why restarting so often makes a problem disappear for a while. What the process did to the world does not go back. The messages it sent stay sent.
Then there is time, which a process spends in two quite different ways. Each check takes a moment of real work: fetch the page, find the price, compare. Then time.sleep(600), and for ten minutes the checking thread sleeps and does no work, while the listener thread can still run. Leave one of these processes running for a week and it will have existed for 168 hours while spending a small fraction of that actually executing instructions. In a terminal, ps -o pid,etime,time -p followed by a process ID shows both clocks: ELAPSED, how long the process has been alive, and TIME, how long it has spent being worked on. A process can be alive for days and busy for minutes. It is a schedule many of us would accept.
Waiting is not the same as nothing, though. A sleeping process still holds its memory, its connection, its child browser and its place in the loop, and the world does not wait with it. The page can change while it sleeps, a login can expire, the price can rise and fall again between two checks. Being alive, doing work and still being in step with the world are three different conditions.
The process is what you answer for
On 27 July 2012, Knight Capital, then one of the largest traders in US stocks, began rolling out new trading code to the eight servers that ran one of its order-routing systems. According to the US Securities and Exchange Commission’s later account, a technician copied the new code to seven of them. The eighth still held old code that had not been used in years, and on 1 August 2012 a setting reused by the new release switched that old code back on. In about 45 minutes it sent millions of orders into the market, and the firm lost more than $460 million. The program in Knight’s release was the one they meant. What ran was eight servers, separate computers each running processes of its own, and on one of them those processes were executing a different program entirely.
You will probably never move $460 million in 45 minutes, but the shape is the same at every size: what you meant, what you wrote, and what is actually running can each differ from the others, and only the last one touches the world. Vibe coding widens the distance between them. The AI writes the program, and often it starts processes too: a development server, a test watcher, a background worker, started from its own terminal to check its work. The conversation ends. The processes do not. Wednesday’s run, started only to check that the bot worked, was still checking on Thursday. Along the way it makes decisions that are not in your prompt, a thread here, a child browser there, and each of those decides what the process holds, how long it lives and what stopping it actually stops. Those are not only implementation. They are choices about the process, made quietly on your behalf.
None of this requires reading every line. It requires asking a few questions that work on any software, including software you will never read, and including the AI’s own: how many of this are running right now? What does each one hold? What does it reach out and touch? When I stop it, what ends with it, and what keeps going?
The fix for Thursday’s three messages is not in the file. The file was right all along. The fix is knowing there were three processes, and that each of them was yours from the moment you pressed Enter. The AI will hand you the program in a minute, whenever you ask. The process it becomes is the part that runs, remembers and reaches, and that part needs you.
You can build it. Now understand it.


