Skip to content

Sending it to the platform

A history on your disk and a history on the platform are two ends of one lineage. push carries a branch one way, pull carries it the other, and neither end keeps a record of the other.

import chatty_lab as cl
here = cl.Workdir(cl.Store.at("./work"), graph=LOCAL_ID)
there = cl.Store.on("https://chatty-lab.com", token=TOKEN, graph=REMOTE_ID)
carried = here.push(there)
carried.sent # [Version, …] — what the far end did not have, oldest first, in ITS ids
carried.head # Ref — where the branch points over there afterwards

Push the same branch twice and sent comes back empty. Nothing was missing.

  1. An address. Either the API root or the address of the site — Store.on adds the /api if it is not there.

  2. A token. The same Supabase access token the REST API takes. Read it from the environment; do not put it in the script.

  3. The lineage id over there. It is the graph parameter in the Studio URL: …/studio?graph=3f47b2c8-…&branch=main. A graph created with POST /api/graphs returns the same id.

  4. A workspace, if the graph belongs to a team rather than to you. Store.on(..., workspace=TEAM_ID) sets the X-Workspace-Id header on every call.

import os
import chatty_lab as cl
there = cl.Store.on(
os.environ["CHATTY_API"],
token=os.environ["CHATTY_TOKEN"],
graph=os.environ["CHATTY_GRAPH"],
workspace=os.environ.get("CHATTY_WORKSPACE"),
)

Every store mints its own ids, so one version has a different uuid at each end and always will.

local = here.at("HEAD")
remote = carried.sent[-1]
local.id == remote.id # False — always
local.digest == remote.digest # True — this is the version being the same version
local.n == remote.n # maybe. Do not rely on it.

The ordinal is minted by whoever writes: the same content can be v2 here and v3 there. See n is local.

carried = here.pull(there)
carried.sent # what arrived HERE, oldest first

pull is push with the ends the other way round. It brings the history and stops: there is no working copy to land it in, so what arrived is read with content().

here.pull(there)
blob = here.content("HEAD").graph

The workdir’s own branch is the one carried. Another line is spelled the same way it is everywhere else:

here.push(there) # main
here.on("stricter").push(there) # the variant

A push is refused when the branch over there holds versions this one has never seen. That is git’s non-fast-forward, and it wants a merge rather than a flag:

try:
here.push(there)
except cl.SyncError:
here.pull(there) # bring their side in
here.merge("main", message="…") # decide what the two lines make together
here.push(there)

Two other SyncErrors are not divergence at all and are worth retrying rather than merging: the far branch moved between reading where it was and writing, or an ordinal was taken by another writer against that same store in the moment between. Nothing was written either time; asking again decides afresh against what is there now.

Open the graph in the Studio at …/studio?graph=<id>&branch=<name> and the branch you pushed is there, with its versions, their messages, and the diff between any two. A version pushed from a script and a version committed from the canvas are the same kind of thing — the canvas cannot tell which one made it, because there is nothing to tell.

An experiment can then be pinned to one of those versions, and the numbers it produces belong to it. See End to end.