Skip to content

Anthropic built Scion for itself. Carbon Forge is yours →

Carbon Forge
Menu

Guide

Answer a lane conflict in Carbon Forge

Two agents edit the same page. One lands first. Carbon Forge does not guess what the second one meant: it names the conflict, shows both sides, and waits for an answer that is bound to the trunk the answerer actually read.

Also in: Español, Português, 日本語, Deutsch, Français, 한국어, 简体中文, Italiano, हिन्दी

Disclosure: we build Carbon Forge at 100monkeys.ai, so read this as a maker's account. This guide was drafted by one of our AI agents from the public docs, chiefly steps 6 to 8 of Working in lanes and the Lanes catalog page, and reviewed by the company. Every call is the docs' own example. Where the docs leave a question open, the last section says so.

Step 1. Know what counts as a conflict

A lane is a set of patches over the trunk, and every read on it composes the trunk as it stands now with those patches. A patch is in conflict "when it no longer applies to the trunk: its old text no longer matches or matches twice", or the trunk changed a tree entry or library entry the patch replaced. A patch that still applies is not a conflict even if the trunk changed the same section; the read reports it as a moved section, "worth a reviewer's look, not a conflict." Two agents changing different parts of one file both land. And the rule the rest of this guide follows: "Nothing merges by itself."

Step 2. Notice that a lane is in conflict

lanes.get answers landable, which is true only for an open lane with at least one live patch and no conflict. In the app, the lane's page at /<workspace>/lanes/<label> has a Conflicts tab. Without polling, a lane.conflict webhook event names the lane and the paths of its conflicting rows when a landing on the trunk first puts it in conflict.

Step 3. Read the conflict

lanes.conflicts { "label": "agent/7/fix-login" }

Each row carries what a merger needs: the patch; the section as it stood when the patch was taken; the trunk's section now; the lane's composed view; and the changesets that moved it, each with its intent, its actor and the lane that landed it. For a code tree the row adds a unified diff of each conflicting file, and for a language with a grammar an advisory list of the top-level definitions that differ, which "decides nothing". The tool is read-only and needs only the viewer role. In the app, the Conflicts tab shows the trunk text and the lane text with the exact call to answer each one, "ready to copy, while the page itself decides nothing."

Step 4. Answer a text conflict

lanes.resolve { "patch_id": "…", "note": "kept both sign-in options", "old_string": "…", "new_string": "…" }

The answer is a new patch with a note of 1 to 1,024 characters saying why. It is "matched against the trunk as it stands now and bound to the hash of that state." The old patch is kept as history, marked superseded. A tree conflict is answered the same way with upserts, removals or patches, and a library entry with a sha256. Answer in order: a later patch on the same entity waits until the earlier one is resolved.

Step 5. Or drop the patch

lanes.drop_patch { "patch_id": "…" }

When the trunk already did what the patch meant to do, or the patch is no longer wanted, drop it instead. Where the entity the patch was taken against is gone, the docs' refusal says to drop the patch rather than resolve it.

Step 6. Answer a move conflict

lanes.resolve { "patch_id": "…", "note": "kept the trunk's rename and moved on from it", "to_path": "guides/sign-in" }

A lane that renames a page conflicts when the trunk has moved the same page since, when the trunk now holds the new path, or when the page is gone. Answer with to_path, and title where the title should change. The resolution is bound to the path the trunk holds now, so a later trunk move makes it stale as well.

Step 7. Answer conflicts in assembled work

lanes.merge { "from": "agent/8/tests", "into": "agent/7/fix-login", "intent": "assemble the login fix" }

A lead agent gathers several agents' lanes into one before landing. A patch carried this way conflicts where the receiving lane changed what it changes, and lanes.conflicts names, as moved_by, the receiving lane's earlier patches it now stands on. Its resolution answers the receiving lane's view, not the trunk alone.

Step 8. Land, with a dry run first

lanes.land { "label": "agent/7/fix-login", "intent": "fix: accept magic links", "dry_run": true }
lanes.land { "label": "agent/7/fix-login", "intent": "fix: accept magic links" }

The landing is checked again inside its own transaction and refused whole, every conflict named, if anything moved. A resolution answers one exact state of the trunk; if the trunk moves that section before the landing, the resolution goes stale and the landing is refused with:

resolution <id> was resolved against <hash>; the trunk's section is now <hash>

Then read lanes.conflicts again and answer the fresh state. On a protected trunk with landing rules, remember that any resolution after the approvals makes them "count for nothing", so approval comes after the last answer. Send the landing with an idempotency_key and a retry after a timeout answers the first landing's result instead of landing twice.

For a lane that carries a code change and its documentation together, read our guide Land a code change and its documentation in one changeset.

What the docs leave unanswered

No silent merges. Every answer has a note.