---
title: "Manage streams"
description: "Load streams attached or kept, choose which stream in a project to run, and stop, start and remove them."
type: "docs-page"
url: "https://tatolab.com/docs/run/manage"
site: "Tatolab"
section: "Run"
---

# Manage streams

> Load streams attached or kept, choose which stream in a project to run, and stop, start and remove them.

Every stream runs inside the machine's runtime. The `tatolab` CLI asks the runtime to load, stop and forget streams; it never runs a stream itself. This page covers those commands, and the difference between an attached stream and a kept one.

## Three commands that load a stream

| Command | What happens | Use it when |
|---|---|---|
| `tatolab dev` | Loads the stream attached, and loads it again every time you save a `.py` file or `pyproject.toml` in the project | You're writing or changing the stream |
| `tatolab run` | Loads the stream attached, once | You want to run it for a while and watch its log |
| `tatolab run -d` | Hands the stream to the runtime to keep, and returns | It should keep running on its own |

All three run from the project folder and use the project's `.venv`. The stream's log records print in your terminal for `dev` and `run`.

### Attached

An attached stream lives as long as the command that loaded it. Press Ctrl-C, close the terminal, or kill the command, and the runtime stops the stream. If the runtime itself goes down, `run` exits with an error that says so; `dev` waits for the runtime to come back and loads the stream again.

### Kept

A kept stream belongs to the machine. The runtime writes a record of it to its state folder: the graph your stream function built, the project's Python environment, and the exposure of each port. It loads the stream again every time the runtime starts: at login, at boot, and after a crash. If you built from source, the runtime is the `tatolabd` in your terminal, so kept streams come back when you start it again.

```sh
tatolab run -d
```

```txt title="Output"
motion_watcher kept (project /home/you/motion-watcher)
```

## Which stream to load

By default, `run` and `dev` load the one function decorated with `@stream` in the project's `stream.py`. To load something else:

| You want | Run |
|---|---|
| One of several streams in `stream.py` | `tatolab run stream.py:recorder` |
| A stream in another file | `tatolab run cameras.py:front_door`, or `tatolab run -f cameras.py` if it has only one |
| A stream from an installed package | `tatolab run acme_streams.rover:camera_rig` |
| A project in another folder | `tatolab run --dir ~/projects/motion-watcher` |
| The same stream under another name | `tatolab run --name motion_watcher_test` |

`--dir` sets the project folder, where the runtime looks for `.venv`. It defaults to the current folder, and doesn't search parent folders.

A stream's name is its function's name, made URL-safe, unless you pass `--name`. Names are unique on a machine: loading a second stream with a name already in use is refused, and the message says which project has it.

## See what's loaded

```sh
tatolab streams
```

```txt title="Output"
NAME            STATE     NODES  PROJECT
motion_watcher  kept      6      /home/you/motion-watcher
front_door      attached  4      /home/you/cameras
garage          stopped   -      /home/you/cameras
```

| State | Means |
|---|---|
| `attached` | Running, and lives as long as the command that loaded it |
| `kept` | Running, and the runtime loads it again whenever it starts |
| `stopped` | A kept stream you stopped. It stays stopped, across restarts too, until you start it. |
| `failed` | A kept stream the runtime stopped loading. The reason is printed below the table. |

## Stop, start, remove

```sh
tatolab stop garage     # unload it; a kept stream stays stopped until you start it
tatolab start garage    # load a stopped stream again, or retry a failed one
tatolab rm garage       # unload it and forget it
```

`stop` on an attached stream ends it, the same as pressing Ctrl-C in its terminal. `rm` is the only command that makes the runtime forget a kept stream.

## Change a kept stream

The runtime keeps the graph from the moment you ran `tatolab run -d`. Editing the project afterwards doesn't change the running stream, and neither do changes an agent makes to the live graph: after a restart, the runtime loads the recorded graph.

To pick up changes, run `tatolab run -d` again from the project folder. For the same project and stream function, that replaces the record with the new version.

A kept stream holds its name even while it's stopped, so to work on one with `tatolab dev`, remove it first (`tatolab rm motion_watcher`), and `tatolab run -d` again when you're done. Or run the development copy under another name (`tatolab dev --name motion_watcher_dev`), as long as the copy doesn't need a device the kept stream has open, such as a camera.

## When a kept stream fails

A kept stream is marked **failed**, and skipped when the runtime starts, when:

- it was involved in the runtime's last two crashes in a row (a clean stop or restart of the runtime resets the count), or
- it can't be loaded at all, for example because its project's `.venv` was deleted.

Every other stream still starts. `tatolab streams` shows the reason:

```txt
garage failed: <the reason>. `tatolab start garage` retries it, `tatolab rm garage` forgets it.
```

Fix the cause, then `tatolab start garage`.

## The edit loop in detail

`tatolab dev` watches the project's `.py` files and `pyproject.toml`. After each save:

- the runtime runs your stream function again in the project's `.venv`;
- if that works, it replaces the running stream with the new one;
- if it fails, for example a syntax error in `stream.py` or a node module, the error prints and the previous version keeps running until a save that works. If no version has run yet, `dev` prints `no stream is loaded — fix it and save again`.

Errors that happen once the stream is running, like a GLSL mistake in an effect's `setup`, appear in the stream's log, in the same terminal.

## The runtime

Every command on this page needs the runtime running. Without it, each one stops at once:

```txt
error: no runtime is running on this machine: nothing answers at /run/user/1000/streamlib/local-api.sock. Start one by running `tatolabd` in a terminal.
```

How the runtime starts depends on how you installed it: the Tatolab app on a Mac, a systemd user service on Linux, or `tatolabd` in a terminal for a source build. See [Install](https://tatolab.com/docs/install).
