---
title: "Quickstart"
description: "Create a stream, run it, see the output it shares with other streams and agents, and change it while it runs. About five minutes once Tatolab is installed."
type: "docs-page"
url: "https://tatolab.com/docs/quickstart"
site: "Tatolab"
section: "Start here"
---

# Quickstart

> Create a stream, run it, see the output it shares with other streams and agents, and change it while it runs. About five minutes once Tatolab is installed.

You'll create a project with `tatolab new`, run its stream, see the output it shares, and change the stream while it runs.

![What you'll run: your camera, through an effect that inverts its colors, shared so other streams and agents can use it.](https://tatolab.com/media/docs/quickstart-light.jpg)

## Before you start

You need three things:

1. **Tatolab installed, with its runtime running.** Check with:

   ```sh
   tatolab streams
   ```

   If it prints `No streams in this runtime.`, you're ready. If it says `no runtime is running on this machine`, see [Install Tatolab](https://tatolab.com/docs/install). If you built from source, the runtime is `tatolabd` running in a terminal of its own: keep it running, and use another terminal for the steps below.

2. **uv**, the Python package manager. `uv --version` should print a version. If it doesn't, see [Install uv](https://tatolab.com/docs/install/python#install-uv).

3. **A camera.** No camera on this machine? Add `--test-pattern` in step 1 and the stream uses color bars instead.

## 1. Create a project

```sh
tatolab new my-stream
```

```txt title="Output"
Created a Tatolab stream project in `my-stream`.

Next:
    cd my-stream
    uv sync
    tatolab dev
```

It wrote a small stream: your camera, through an effect that inverts its colors, exposed so other programs can read it. It also opens a window, so you can see the result while you test, and logs the picture's brightness once a second:

```txt
my-stream/
├── stream.py                 the stream: which nodes, and how they connect
├── nodes/
│   ├── __init__.py
│   ├── inverting_effect.py   inverts the picture's colors
│   └── brightness_meter.py   logs the picture's brightness
├── pyproject.toml            depends on tatolab-stream and numpy
├── .python-version           3.12
└── .gitignore
```

`tatolab new` refuses to write into a directory that already has these files, so it never overwrites your work.

## 2. Install the project's dependencies

```sh
cd my-stream
uv sync
```

This creates `.venv/` inside the project, with Python 3.12, `tatolab-stream` and numpy. The runtime runs your nodes with this environment's Python, so a package you add with `uv add` is available to every node in the project.

## 3. Run it

```sh
tatolab dev
```

A test window opens with the effect's output: your camera, colors inverted. The terminal says the stream loaded, then shows its log records, one per line, timed in UTC. Once a second, one reports the picture's brightness:

```txt title="Output"
tatolab dev: main loaded (4 nodes, project /home/you/my-stream); Ctrl-C stops it
14:03:22.511 [ INFO] [Rk3x9q2m…/main/python] streamlib::polyglot::python — brightness processor_id=P… height=720 mean=118.4 width=1280
```

The first load can take a few seconds. `tatolab dev` keeps running and watches your files; leave it running.

> **Note: On a Mac**
>
> The first run asks for permission to use the camera: for Tatolab if you installed the app, or for your terminal app if you built from source. Click Allow and the picture appears; you don't need to restart anything.

## 4. See what it shares

Open a second terminal. The stream runs in the machine's runtime, not inside your first terminal:

```sh
tatolab streams
```

```txt title="Output"
NAME  STATE     NODES  PROJECT
main  attached  4      /home/you/my-stream
```

The stream is called `main`, after its function in `stream.py`. Open `stream.py` and look at its last line:

```python
stream_builder.expose(effect.output("video_to_downstream"))
```

That **exposes** the effect's output, your camera with its colors inverted, at the address `main/invertingeffect/video_to_downstream`: the stream, the node and the port. Exposing is how a stream shares what it makes:

- **Other streams use it.** A stream in any project on this machine reads it with `stream_builder.remote_output("main/invertingeffect/video_to_downstream")`, and can record it, run a model on it or send it on. `main` doesn't change, and doesn't need to know who's reading. The [tutorial](https://tatolab.com/docs/tutorial) builds a stream that reads another.
- **Agents see it.** An agent connected to the runtime can look at what the output carries. [Agents and MCP](https://tatolab.com/docs/share/agents) shows how to connect one.
- **Browsers and your other machines get a URL.** The runtime's MoQ relay serves each exposed output at one. See [Across machines](https://tatolab.com/docs/share/across-machines).

## 5. Change it while it runs

With `tatolab dev` still running, open `nodes/inverting_effect.py`. The effect is one small function that runs for every pixel of every frame:

```glsl
vec4 effect(vec4 source, ivec2 at) {
    // Color channels only — inverting alpha would erase the picture.
    return vec4(1.0 - source.rgb, source.a);
}
```

Replace it with a grayscale:

```glsl
vec4 effect(vec4 source, ivec2 at) {
    float gray = dot(source.rgb, vec3(0.299, 0.587, 0.114));
    return vec4(vec3(gray), source.a);
}
```

Save the file. `tatolab dev` notices and loads the stream again. Each load replaces the whole stream, so the window closes and opens again, now in gray.

If your GLSL doesn't compile, the effect can't start: the terminal shows the error, naming the line of your GLSL that failed, and no picture reaches the window until you fix it and save again. An edit that stops the stream loading at all, such as a Python syntax error in `stream.py`, is different: `dev` keeps the last working version running until you save one that loads.

## 6. Stop it

Press **Ctrl-C** in the first terminal. The stream stops and the window closes. The runtime keeps running, ready for the next stream.

## What just happened

- `tatolab dev` asked the runtime to load `stream.py`. The runtime ran your `@stream` function with the project's `.venv` to learn which nodes to create and how to connect them, then started them.
- The camera and the window are **built-in nodes**, native code inside the runtime. Your two nodes each ran in **their own Python process**, started from your `.venv`, so neither could stall the other or the window.
- The stream exposed its output by an address. Other streams read it by that address, agents can look at it, and the MoQ relay serves it at a URL.
- `dev` watched your files and reloaded the stream when one changed. `tatolab run` does the same without the watching, and `tatolab run -d` hands the stream to the runtime to keep running after you close the terminal.

## Next steps

- [How Tatolab works](https://tatolab.com/docs/how-it-works): streams, nodes, exposure and the runtime, in plain terms.
- [Tutorial](https://tatolab.com/docs/tutorial): write a node of your own, and share its output with a second stream.
- [Write a node](https://tatolab.com/docs/build/nodes): everything a node can declare and do.
