Branching experiments
A worked example. Fork a notebook, develop two approaches, compare them, and converge.
This walks one branching workflow end to end, by hand, with code you can paste. For the ideas behind it (what a fork carries, the branch graph, convergence), read Branches first.
The setup: you load a dataset, split it, and fit a logistic-regression baseline. Then you want to try a gradient-boosted model against it without redoing any of that.
One notebook, forked at the last shared cell: three arms run side by side on their own kernels, and come back as a single comparison.When to fork by hand, and when not to
If your experiment is a parameter sweep ("try these three learning rates"), skip the manual forking and ask the agent for a branched experiment: it forks a branch per variant from the same starting state, runs them in parallel, and reports back. Same shape of work, one instruction.
Fork by hand when the branches differ structurally rather than parametrically: different model families, different feature sets, different framings of the problem. That's this example. A sweep varies a number; a fork lets each arm be a different piece of code.
1. Get to the fork point
Work the notebook up to the cell where the two paths split. Here that's the train/test split, and it's the last thing both approaches share:
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=0
)
scaler = StandardScaler().fit(X_train)The Breast Cancer Wisconsin dataset ships with scikit-learn, so there's nothing to download. Then the baseline, on the branch you're already on:
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
logreg = LogisticRegression(max_iter=5000).fit(scaler.transform(X_train), y_train)
p = logreg.predict_proba(scaler.transform(X_test))[:, 1]
print(accuracy_score(y_test, p > 0.5), roc_auc_score(y_test, p))
# 0.9825 0.99572. Fork
Hover the gap under the last cell and click Branch. Clusy copies the kernel into the new branch, so it opens with X_train, y_test, scaler and the rest already in memory. Name it something you'll recognize: "gbm" beats "branch 2".
Fork now, before you add anything else to this branch — a fork is cut from a branch's latest cell, so once you've moved on you'd be forking from the newer state instead.
You now have two branches sharing the setup and diverging from that cell. The baseline branch is untouched, and its logreg is still sitting in its own kernel.
3. Build the new branch
On the new branch, write the gradient-boosted approach. The first cell can use X_train immediately, because it came along in the copy:
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import accuracy_score, roc_auc_score
gbm = HistGradientBoostingClassifier(random_state=0).fit(X_train, y_train)
p = gbm.predict_proba(X_test)[:, 1]
print(accuracy_score(y_test, p > 0.5), roc_auc_score(y_test, p))
# 0.9386 0.9868Nothing above the fork ran again. Note also what the fork did not have to carry: logreg was fitted after the fork point, on the other branch, so it isn't here — which is exactly what you want, because it keeps the two arms clean.
4. Compare
Switch between the branches from the branch control at the top right of the canvas, or click the fork pills that appear inline at the split cell. Both branches started from the same state, so they saw the same split of the same data, and the comparison is honest:
| Accuracy | ROC AUC | Fit time | |
|---|---|---|---|
| Logistic regression (baseline) | 0.982 | 0.996 | ~0.01 s |
| Gradient boosting | 0.939 | 0.987 | ~0.4 s |
The boosted model loses, on both metrics, and takes longer. That's a real result on this dataset: 30 well-behaved numeric features and 569 rows is a linear model's home turf, and boosting has nothing to exploit. Worth remembering the next time a comparison card puts a Fastest badge on the quickest run and it happens to be the simplest one too. The badge is a stopwatch, not a verdict; the reading is yours.
5. Converge
Once you've decided, ask the agent to converge the branches. The cell they rejoin shows a bar across the top with a chip for each variant that feeds it — a diamond in the graph saying these split at the train/test cell and came back together here.
Converging records the decision; it doesn't move anything. No variables, files, or data are merged between branches. To carry on from the baseline, carry on on the baseline branch, where logreg and everything else already lives. The convergence is there so the notebook shows why.
One consequence: a converged branch seals the history feeding the join. If you need to change or re-run a cell upstream of it, clear the convergence first, then re-record it.
Tips
- Fork at the moment of divergence. A fork is cut from the branch's latest cell, so cut it before you start building one of the two approaches — not after.
- Mind the cap. Three concurrent branches on CPU, eight on a GPU, counted across the whole project. Deleting a branch is the only thing that frees a slot. See Limits.
- Watch what a fork can't carry. Open handles, database connections and device contexts are excluded from a successful copy, and a namespace over 4 GiB cannot become a durable checkpoint. If the live copy itself fails, Clusy rolls the attempted child back instead of publishing a fresh-kernel variant. See what a fork doesn't carry.
- Mind the meter on GPU sweeps. Auto on CPU is unmetered on paid plans and consumes the allowance on Free. N variants on a GPU is roughly N times the compute, and the sandbox meter runs the whole time. See Usage.
- Name branches. A tree of "branch 1, 2, 3" is its own puzzle.
- Branching isn't committing. When you've got a result worth versioning, commit the notebook to GitHub.