Frame: The Unit of Composition

Learn about Frames, the primary data structure for grouping and managing schedules in τ.

A Frame is a fundamental data structure in τ (tau). It acts as a container, grouping together one or more Schedules. Think of it as the primary unit of composition and storage. When you query τ, you are interacting with Frames.

Each Frame is assigned a unique, time-sortable identifier (a ULID) upon creation, ensuring that every Frame can be uniquely referenced.

The Structure of a Frame

A Frame is composed of two main components:

  • id: A unique []const u8 identifier for the Frame.
  • schedules: An array of Schedule structs.

This structure allows a single Frame to hold multiple, independent timelines of data, which can be queried and reasoned about collectively.

libtau/primitives/frame.zig
1/// A frame represents a collection of schedules identified by a unique id.
2pub const Frame = struct {
3  id: []const u8,
4  schedules: []Schedule,
5  // ... methods
6};

Core Operations

Frames are designed to be append-only, ensuring data immutability and a clear audit trail.

1. Creating a Frame

You can create a new Frame by providing an allocator and an initial slice of Schedules. The create function will automatically generate a new ULID for the frame and perform a deep copy of the provided schedules.

Example: Creating a Frame
1const std = @import("std");
2const tau = @import("libtau");
3
4const allocator = std.heap.page_allocator;
5
6// 1. Create a Tau (the actual data point)
7const my_tau = try tau.primitives.Tau.create(allocator, 42.0, 0, 1000);
8defer my_tau.deinit(allocator);
9
10// 2. Create a Schedule containing the Tau
11const my_schedule = try tau.primitives.Schedule.create(allocator, "sensor_a", &[_]tau.primitives.Tau{my_tau});
12defer my_schedule.deinit(allocator);
13
14// 3. Create a Frame containing the Schedule
15const my_frame = try tau.primitives.Frame.create(allocator, &[_]tau.primitives.Schedule{my_schedule});
16defer my_frame.deinit(allocator);
17
18std.debug.print("Created Frame with ID: {s}\n", .{my_frame.id});

A Frame cannot be empty. The create function asserts that you must provide at least one Schedule.

2. Adding Schedules to a Frame

The addSchedules function allows you to append new Schedules to an existing Frame. This operation is also append-only and performs a deep copy of the new schedules, preserving the immutability of the original data.

Example: Adding Schedules to a Frame
1// ... assuming my_frame exists from the previous example
2
3// Create a second schedule
4const new_tau = try tau.primitives.Tau.create(allocator, -15.5, 500, 1500);
5defer new_tau.deinit(allocator);
6const new_schedule = try tau.primitives.Schedule.create(allocator, "sensor_b", &[_]tau.primitives.Tau{new_tau});
7defer new_schedule.deinit(allocator);
8
9// Add the new schedule to the frame
10try my_frame.addSchedules(allocator, &[_]tau.primitives.Schedule{new_schedule});
11
12std.debug.print("Frame now contains {} schedules.\n", .{my_frame.schedules.len}); // Output: 2

Serialization and Deserialization

Frames can be serialized into a compact binary format for storage or network transmission. The serialize and deserialize methods provide an efficient way to manage the lifecycle of a Frame.

The binary format is structured as follows:

  1. id_len (u32, big-endian)
  2. id ([]u8)
  3. schedules_count (u32, big-endian)
  4. For each schedule: a. schedule_bytes_len (u32, big-endian) b. schedule_bytes ([]u8)

This simple, explicit format ensures that Frames can be read and written efficiently by any part of the τ ecosystem.

Last updated: 2/1/2026