Turn anything
into a live stream.
Wire cameras, models and your own code into a stream. Expose an output, and apps, agents and your other machines read it by URL.
brew install --cask tatolab/tap/tatolabopen -a Tatolabcurl -fsSL https://get.tatolab.com/install.sh | sh Apple silicon, macOS 15 or laterx86_64 Linux with a Vulkan GPU
claude mcp add tatolab -- tatolab mcpEvery exposed output has a URL. Open it in a browser, pull it into another stream, or hand it to an agent.
An agent connected over MCP samples the output and looks at a frame.
Change stream.py while tatolab dev runs, and the feed follows.
overlay = stream_builder.add(
Overlay, config={"only": "sports ball"}
)Save it, then watch the video.A stream is a function. A node is a class. Wrap a model, an SDK or a driver in a node. Each one runs in its own process.
@stream
def pitch(stream_builder: StreamBuilder) -> None:
camera = stream_builder.remote_output("pitch-cam/sideline/camera/video")
detector = stream_builder.add(
HuggingFaceDetector, config={"model": "facebook/detr-resnet-50"}
)
overlay = stream_builder.add(Overlay)
stream_builder.connect(camera, detector.input("video_from_upstream"))
stream_builder.connect(camera, overlay.input("video_from_upstream"))
stream_builder.connect(
detector.output("detections_to_downstream"),
overlay.input("detections_from_upstream"),
)
stream_builder.expose(
overlay.output("video_to_downstream"), Exposure.PUBLIC
)@node
class HuggingFaceDetector:
@node.input(delivery_profile="newest")
def video_from_upstream(self) -> VideoFrame: ...
@node.output()
def detections_to_downstream(self) -> dict: ...
def __init__(self, config: HuggingFaceDetectorConfig) -> None:
self.model = config.model
def setup(self, ctx: RuntimeContextFullAccess) -> None:
self.detect = pipeline("object-detection", model=self.model)
def process(self, ctx: RuntimeContextLimitedAccess) -> None:
frame = ctx.inputs.read("video_from_upstream", into=VideoFrame)
if frame is not None:
with frame.cpu() as pixels:
objects = self.detect(Image.fromarray(pixels[:, :, :3]))
ctx.outputs.write("detections_to_downstream", {"objects": objects})Works with your agents. Over MCP, an agent sees the live graph, samples outputs, looks at frames, reads logs, and rewires nodes.
- Over MCP
Claude Code
Claude Desktop
Cline
- Codex
Cursor
- Devin Desktop
Gemini CLI
GitHub Copilot
- Grok Build
Zed
- Computer use
- Grok Bot
- Muse
They open a stream by its URL.
- Set up
claude mcp add tatolab -- tatolab mcpOne command or a few lines of config for each agent. Connect an agent
Nothing leaves until you expose it. Set each output's level in code, and change it while the stream runs.
- Internal
Only links inside the stream read it. Every output starts here.
- Private
Streams and agents on this machine read it.
stream_builder.expose(output)- Public
Your other machines read it by URL, over your tailnet or a relay.
stream_builder.expose(output, Exposure.PUBLIC)- Live
Raise or remove a level without a restart.
$ tatolab expose pitch overlay video_to_downstream --remove
Outputs travel over MoQ on QUIC, which is always encrypted: across your tailnet, or through a relay you run or join.
Install it. The quickstart has a stream running in about five minutes.
brew install --cask tatolab/tap/tatolabopen -a Tatolabcurl -fsSL https://get.tatolab.com/install.sh | sh Apple silicon, macOS 15 or laterx86_64 Linux with a Vulkan GPU
Questions? Email hello@tatolab.com. Agents can read this page as Markdown at /index.md, and /llms.txt lists the rest of the site.