Skip to main content
Merging happens when a workspace and a branch both changed after the workspace was created. The workspace is where agents resolve conflicts. The normal loop is: snapshot local work, sync the branch into the workspace, fix any conflicts, snapshot again, then promote. Tensorlake never force-overwrites a branch. If a merge cannot land cleanly, promotion publishes nothing unless you explicitly choose a mode that materializes conflict markers.

Promote a Workspace

If the target branch has not moved since the workspace was created, promotion lands the workspace on that branch:
$ tl fs promote /work main -m "implement parser"
Promoted workspace 3f9a2b7e1c4d -> main at 1c4d9a2f7e5b3d8c6a4f2e0d9b7c5a3f1e8d6b4c (squashed)
If the branch moved, a plain promotion fails instead of overwriting someone else’s work. Add --merge when Tensorlake should merge the workspace with the latest branch tip:
$ tl fs promote /work main --merge
Promoted workspace 3f9a2b7e1c4d -> main at 7a1c3e5b9d2f4b6d8e0a2c4f6e8b0d3a5c7e9f1b (merge)
When the changes do not overlap, this lands one merge commit combining both histories. If the branch did not move, it fast-forwards like a plain promotion.

When Promotion Conflicts

If the workspace and branch changed the same content, promotion publishes nothing. The target branch and workspace stay unchanged, and the command reports each conflicted path:
$ tl fs promote /work main --merge
error: promote to main conflicts on 2 path(s); nothing was published:
  content        src/parser.py
  delete_modify  notes/plan.md
Resolve the conflict in the workspace:
  1. Run tl fs sync /work.
  2. Edit the conflicted files.
  3. Snapshot the resolved files.
  4. Promote again.

Sync the Branch into a Workspace

tl fs sync pulls the target branch into the workspace. The mounted directory updates in place:
$ tl fs sync /work
Synced with 9c4e2a7b1f3d (12 path(s) pulled).
If the sync conflicts, Tensorlake writes standard Git diff3 markers into the workspace files:
$ tl fs sync /work
Synced with 9c4e2a7b1f3d (12 path(s) pulled).
note: 1 conflict(s) materialized as diff3 markers:
  content        src/parser.py
Resolve the markers, then `tl fs snapshot /work`.
The file carries the branch version, the merge base, and the workspace version:
<<<<<<< main
def parse(text: str) -> Ast:
||||||| base
def parse(text):
=======
def parse(text, *, strict=False):
>>>>>>> workspace 3f9a2b7e1c4d
Edit the file to resolve the conflict, then snapshot and promote:
$ tl fs snapshot /work -m "resolve parser conflict"
$ tl fs promote /work main --merge
Promoted workspace 3f9a2b7e1c4d -> main at 4b6d8e0a2c4f6e8b0d3a5c7e9f1b7a1c3e5b9d2f (fast-forward)
Sync refuses to run over unsnapshotted local changes because those changes could hide pulled content. Snapshot first. Use --fail-on-conflict when a conflicted sync should change nothing instead of writing markers. Use --target to pull from a branch other than the one the workspace was created from.

Shared Read-write Branches

On a shared read-write branch, every snapshot is merged into the branch automatically. Agents editing different files, or non-overlapping regions of the same file, usually merge cleanly. When two agents change the same content, the snapshot still lands with diff3 markers in the conflicted file and a structured conflict record on the commit. Any agent following the branch can resolve the markers by editing the file and snapshotting. Nothing is silently overwritten. Conflicted commits are flagged in the dashboard’s activity feed, and the record is queryable without parsing file contents:
$ tl git conflicts agent-outputs 7a1c3e5b9d2f
ours: 4b6d8e0a2c4f
theirs: 9c4e2a7b1f3d
base: 5f2c8e1a9b3d
conflicts: 1 path(s):
  content        src/parser.py

Merge Branches Directly

Use tl git merge when you want to merge one branch into another without creating a workspace. Preflight first to see what would happen. It never writes:
$ tl git merge agent-outputs main dev --preflight
merge base: 5f2c8e1a9b3d
changed paths: 12
Clean merge.
A shallow preflight reports same-file collisions as potential conflicts. Add --deep when you need exact text-merge results:
$ tl git merge agent-outputs main dev --preflight --deep
merge base: 5f2c8e1a9b3d
changed paths: 12
conflicts: 1 conflict(s):
  content        src/parser.py (potential)
  (run with --deep for exact content-merge answers)
Drop --preflight to land the merge:
$ tl git merge agent-outputs main dev -m "land dev"
merge base: 5f2c8e1a9b3d
changed paths: 12
Merged dev into main at 7a1c3e5b9d2f
A conflicted direct merge publishes nothing and exits non-zero. Add --materialize to land it anyway with markers and a conflict record, the same contract as shared read-write branches. Add --json for machine-readable reports.

How Merging Runs

Merges run server-side with a three-way merge engine. Tensorlake does not clone the repository, check out a working tree, or scan every file. Merge cost scales with the number of changed paths, not total repository size.

Conflict Kinds

Conflict reports classify each path:
KindMeaning
contentBoth sides edited the same region of a text file.
delete_modifyOne side deleted the path; the other modified it. The modified side is kept.
add_addBoth sides added the path with different content.
kind_mismatchThe path is a file on one side and a directory or symlink on the other.
modeBoth sides changed the file mode to different values.
too_largeA side is binary or over the text-merge size limit; never text-merged.

Next Steps

Writable Workspaces

Mount, snapshot, and promote — the workflow merging builds on.

Architecture

How the merge engine and conflict policies work internally.