Synchronoid is in private beta. New workspaces open in small batches every few weeks. Join the waitlist →

Assets

An asset is a named piece of data that should exist and stay current: a table of companies, a file of meter readings, a set of normalized listings. You describe the asset, and Synchronoid decides when and how to build it.

Defining an asset

An asset is a Python function decorated with @sy.asset. The function name is the asset name. The function receives a context and the data of its inputs, and returns or yields records.

pipelines/companies.py
import synchronoid as sy

@sy.asset(layer="staged", source=companies, retry=sy.Retry(max_attempts=5))
def company_profiles(ctx, raw):
    for item in raw.records():
        yield {"org_id": item["id"], "name": item["name"].strip()}

The decorator describes what the asset is. The function describes how to compute it. This split lets the platform schedule, retry, replay and inspect the asset without understanding your code.

Asset options

OptionTypeMeaning
layer"raw", "staged", "published"Where the output is stored. See storage layers.
sourcesy.SourceThe source whose payloads this asset reads or fetches.
depslist of assetsUpstream assets. Defines the lineage graph.
retrysy.RetryRetry policy for failures in this asset.
partitionssy.PartitionsSplits the asset into independent slices, such as days or years.
keystringRecord key used for merging and change tracking.
descriptionstringShown in the control room.

Dependencies and lineage

When you list other assets in deps, their data is passed to your function as an argument with the same name. Synchronoid builds the lineage graph from these declarations.

pipelines/companies.py
@sy.asset(layer="published", deps=[company_profiles])
def companies_current(ctx, company_profiles):
    return company_profiles.merge(key="org_id", track_changes=True)

The argument is a reference to stored data, not a copy. Large inputs are read lazily in batches, so memory use stays low and runs stay fast.

Materialization

Running an asset is called materializing it. Each materialization produces a new version of the output, and records the run id, input versions and timing. You can materialize assets in three ways:

Materialization is replay-safe. If you run the same asset twice for the same inputs, the second run produces the same output and creates no duplicates.

Write idempotent code

Synchronoid guarantees idempotent storage writes. Your function should not cause side effects outside of Synchronoid, such as sending email, unless you guard them with ctx.run_id.

Partitions

A partitioned asset is split into slices that can be run independently. Use partitions when the data naturally divides by time or by key.

pipelines/meters.py
@sy.asset(layer="staged", partitions=sy.Partitions.daily(start="2026-01-01"))
def meter_readings(ctx, raw):
    day = ctx.partition   # e.g. "2026-03-14"
    ctx.log.info(f"processing {day}")
    yield from normalize(raw.records())

If one day fails, only that partition is retried. The rest are not touched.

Checks and metadata

You can attach checks to an asset with @sy.check, as shown in the tutorial. You can also attach metadata, which appears in the control room next to each materialization:

pipelines/companies.py
ctx.add_metadata({"row_count": n, "oldest_record": oldest.isoformat()})

Asset or step?

Use an asset when you care about the result and want it stored, versioned and visible. Use a step for work inside a pipeline that is only a means to an end. If you are unsure, start with an asset.