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
- What to do when the base section cannot be recovered. The conflict row walks back at most 200 revisions for it and otherwise reports
unknown; the docs do not say how to answer from there. - What the
dry_runlanding answers. The guide shows the call but not its response. - What the
lane.conflictevent carries beyond the lane and the paths.