Skip to content
Workflows / Enrich

Add columns without rewriting the table.

Define a new column once, with a model or an expression. Spiral appends it on the same rows and keeps it current as new rows land.

Video, force, pose, actions, and model output

Enrichmentcontacts/slip_distance
DefinitionObject displacement after stable contactrowcontactunitmillimeters
Lineagecontactsobject_positiontracker_v3refresh new and changed rows
The missing column

Hand-made features go stale and cannot be rebuilt.

Nobody recorded the code or the model, so nobody can compute the next batch the same way.

Chained Enrichments

One Enrichment can read another.

Spiral Robotics, an app built on Spiral, chains Enrichments into a signal graph. It tracks objects, scores progress with WARP-RM, and splits each episode into subtasks, and each step reads the column that the step before it appended.

Signal graphBuilt on Spiral · Spiral Robotics
run modelsbreak downsearchselecttop_cameravideo · per episodeobject_positiontrack × frameprogressWARP-RM · per framesubtaskssubtask × episodesimilar_subtasksneighbors × subtaskslip_distanceyour expression · mmclean_graspstraining set · grasps
Models

A model’s output becomes a new column.

Run a detection model to add object tracks, a classifier to add a class to each clip, or an embedding model to add a vector. Each Enrichment records the model revision that made it, and you can pin a version to compute it again.

Enrichmentepisodes/subtasks
Sourcetop_camera
Modelbreakdown_task
Columnsubtasks

pinned model each version records its definition

The mechanics

Append once, then refresh only what changed.

01A column on the same rowsSpiral appends the output as a column aligned to the source rows. A scan reads it with no join, and nothing rewrites the table.
02Backfill any set of rowsCompute a column for every row you already have, or only for the rows you need. Rows you skip read NULL.
03Refresh only what changedNew rows read NULL until the next refresh. A change to a column that the expression reads makes only those rows stale, and changes to other columns cost nothing to refresh.
The expression

Write the expression once, and Spiral computes it for every new row.

An Enrichment computes one value for each row from the columns of that row. Spiral records how to build and rebuild each Enrichment column, so new rows can be enriched incrementally, the same way as old ones.

Python · contacts/slip_distance

Docs
import spiral
from spiral import call, column

client = spiral.Client()
main = client.project("robotics").branch("main")
contacts = main.table("contacts")

# Each contact row holds 2 s of object_position.
pos = column("object_position")
first = call("spql.list.first", pos)
last = call("spql.list.last", pos)
slip = call("spql.tensor.l2_norm", last - first)

commit = main.commit()
commit.create_enrichment(contacts, "slip_distance", slip)
commit.publish()

one value per contact object_position in mm, from tracker_v3

What you keep

Each Enrichment records what it was derived from.

Each version stores its expression, including the model, the source columns that it reads, and the version before it. Tags, run records, and checks read it at a version that you pin, not from a copy that can drift.

Lineageepisodes
Sourcestop_camerawrist_cameragripper_ft
Modelsbreakdown_taskwarp_rmtracker_v3
Parent versionv11 · 4be0d917
faqs

Before you add a column.

An Enrichment can use any SPQL function on the columns of its row, and the models that Spiral runs, each pinned to a revision. Each version of an Enrichment records its definition, including the model.

It runs beside the data, on your own compute. Spiral schedules the work on the recordings where they are.

Yes. Compute columns over public, partner, or cross-cloud data. Large objects such as video stay where they are.

You publish a new version of the Enrichment, and Spiral computes it for every row. Each training set stays on the old version until you move it, and the source recordings do not change.

No. You can fill it for only the rows that you need and backfill the rest later. A refresh computes only the rows whose inputs changed.