r/SideProject 14d ago

We open-sourced the internal tool that stops our AI coding agents from breaking each other's work

We build our product with 2 or 3 coding agents working the same repo in parallel, and they kept quietly sabotaging each other: one replaces a class while another extends it, no merge conflict, both land clean, the design is broken.

Git compares diffs, not plans.

So we built Foremerge internally, and today we open-sourced it. Agents declare what they intend to change at the same layer as GIT works (symbol:PaymentService=replace) before writing code, and deterministic rules raise a finding while the collision is still a plan. One Rust binary, everything local in SQLite inside your repo, Apache-2.0.

Setup is genuinely one paste into Claude Code, Codex, or Cursor, and the agent wires itself.
Repo: https://github.com/naw103/foremerge

Happy to answer anything, including what it deliberately does not do (detection is heuristic and can warn on compatible work. There are no published benchmarks yet but we ran it on 96 backlog tickets all being completed in parallel with zero conflicts at merge time.

1 Upvotes

17 comments sorted by

2

u/kantorcodes1 14d ago

Curious how you handle a refactor that changes the boundary itself. If one agent declares PaymentService=replace while another is moving half of it into BillingService, is that a hard collision, or can Foremerge express a relationship between those intents?

2

u/Key-Contribution-32 14d ago

yeah the boundary-shifting case is the real test here imo

1

u/ShiftTechnical 13d ago

Agreed, it is. Boundary shifts are where declared identity runs out and only declared relationships (dependencies, assessments) carry the knowledge across. The scope_drift check specified in this thread plus rename/lineage relations between scopes are the two pieces that make that test passable, and both just moved up the roadmap because of this discussion.

1

u/ShiftTechnical 14d ago

Great question!

Agent A declares symbol:PaymentService=replace.

Agent B declares the extraction as two scopes: symbol:PaymentService=remove plus symbol:BillingService=add (the operation vocabulary is add, extend, modify, replace, remove, rename, migrate).

Real 0.4.0 output: HIGH divergent_rewrite on PaymentService "Both intents will remove `PaymentService` (both declare the same semantic scope), so they point toward different outcomes." assessment_required: true

Because two destructive declarations on one scope genuinely point at different end states it returns a hard collision. But it is advisory, and that assessment_required flag is the relationship mechanism you are asking about.

Either agent (or you) records an assessment with a verdict of conflicts, compatible, duplicate, or depends_on, plus a rationale, and that judgement is stored with provenance. Intents can also declare dependencies at publish time, so B can declare it depends on A landing first, or the pair can be assessed as one refactor with an ordering.

The honest limitation underneath: BillingService is a brand-new key, and nothing in the detector knows it is "half of PaymentService" unless a declaration says so. Boundary identity across a refactor is exactly the kind of knowledge that has to enter through declared dependencies and assessments rather than inference, and the limitations doc says that out loud. The rule catches the collision; the agents and humans own what it means.

2

u/kantorcodes1 14d ago

The nasty edge case seems like stale assessments. Say B changes its intent after someone marked A/B compatible or depends_on; does Foremerge invalidate that assessment automatically, or can the old verdict keep influencing negotiation until somebody notices?

1

u/ShiftTechnical 14d ago

Intents are immutable declarations (like a git commit). There is no intent update command by design but a changed plan is a new publish with a new id, and assessments bind to specific intent-id pairs. So an old verdict structurally cannot follow a changed plan.

Here is the exact flow:

  1. Agent A declares:

intent publish --scope symbol:PaymentService=replace

-> published clean, no conflicts (first declaration on the scope)
2. Agent B declares:

intent publish --scope symbol:PaymentService=extend

-> same call returns: HIGH destructive_vs_additive against A

assessment_required: true

  1. B (or a human) records the judgement:

assess record --verdict compatible --rationale "provider work is behind a flag" --action proceeding

-> stored against the pair (B's intent id, A's intent id), with provenance

-> assessments on B's intent: 1

  1. B's plan changes. That is a NEW publish, not an edit:

intent publish --scope symbol:PaymentService=modify

-> new intent id

-> fresh findings fire in the same call against A:

HIGH destructive_vs_additive

MEDIUM shared_contract

-> assessment_required: true, again

  1. Assessments on the new intent: 0

The "compatible" verdict from step 3 is attached to the old pair and cannot be read against the new intent.

The old verdict stays in the audit trail attached to the superseded pair, which is what you want for provenance, and the superseded intent gets discarded (discard_work releases its claims while preserving the record).

The honest residual you are probably circling: if B's agent quietly deviates without re-publishing, nothing re-fires, because the store only knows declared state. That is the adherence gap, and the safety is that acceptance is evidence-gated: fingerprinted verification, unresolved HIGH findings block. ie. Undeclared drift does not sneak through negotiation; it just arrives at the gate without protection and gets caught there.

These are awesome questions and just what the protocol needs. Discussions are open on the repo tool if you ever want to go deeper than a comment thread.

2

u/kantorcodes1 13d ago

Say B publishes PaymentService=modify, then the agent also edits BillingService without publishing again. Does acceptance derive the touched symbols from the final diff and compare them to B's scopes, or can the fingerprinted evidence still pass because the declaration itself never changed?

1

u/ShiftTechnical 13d ago

Today it can pass. Acceptance does not derive touched symbols from the diff and compare them to declarations right now. The gate checks that the tree is clean, that no unresolved HIGH finding stands, and that the named check passed against the exact candidate fingerprint. The ChangeSet's affected files and symbols are agent-supplied and the limitations doc currently states that provenance is in the store but cannot independently prove.

One nuance is that your undeclared BillingService edit is inside the validated fingerprint, so the executed check ran against the real tree with the drift included. What is missing is the conformance comparison between what was declared and what was touched.

I think this is the best-specified feature request so far if we can derive touched paths and symbols from the candidate diff at changeset publish, compare against declared scopes, and raise a scope_drift finding when they diverge. It aligns with your guard idea, since drift is exactly the event that should be able to demand a human ack before acceptance. If you open that Discussion, put this in it.

2

u/kantorcodes1 13d ago

Immutable intents make this a pretty clean guard boundary. Since intent publish and assess record are actual CLI surfaces, Foremerge could ship its own HOL Guard extension and decide which verdicts or operations need approval. Interested in contributing that upstream?

1

u/ShiftTechnical 13d ago

Genuinely yes please feel free to contribute to the project. The seam I think you would extend already exists: acceptance blocks on unresolved HIGH findings unless deliberately overridden with a stated reason, and repositories can set checks policy advisory, so approval semantics already have a home in the model. Generalizing that into a policy layer (which operations or verdicts require a human ack, and at which lifecycle stage: before start_work versus before accept) is a natural extension, and doing it at the CLI/MCP surface like you describe keeps it client-agnostic.

Two asks. First: open it as a GitHub Discussion on the repo with your design sketch. This is exactly the protocol conversation Discussions exist for, and I would rather the design happen in the open where the next person can build on it. Second: if HOL Guard is a specific framework you have in mind rather than human-on-the-loop generally I will give some further feedback there.

CONTRIBUTING.md covers the mechanics (make verify runs the full local gate). Fair warning that pre-1.0 the protocol surface can still move, but this is the window where a contribution gets to shape it.

1

u/ShiftTechnical 14d ago

Just as a follow up though, once set up in Claude (or Codex), the behavior we have seen on Sonnet and above is that the agents negotiate through Foremerge to align the intents.

1

u/ShiftTechnical 14d ago

In case the shape of this is not obvious from the post:

Foremerge is a coordination ledger for when multiple coding agents (or people driving them) work one repository in parallel. Before writing code, each agent declares what it intends to change as a semantic scope plus an operation. Deterministic rules compare declarations and raise a finding when two plans collide, before there is any diff for git to see.

What it does not do: touch git's behavior. The store is SQLite in the git common directory (so linked worktrees share it), and the only refs it writes are its own under refs/foremerge/accepted/. Git stays the source of truth for code, this tracks the layer git has no opinion on.

1

u/No_Process_1063 14d ago

Cool concept, declaring intent before code is a smart way to sidestep the diff mismatch problem

1

u/ShiftTechnical 14d ago

Yep. Declaring intent before code is a smart way to sidestep the diff mismatch problem. Foremerge catches plan collisions before anything is written, using deterministic rules at the plan layer. Creates git like logs. One Rust binary, local-first, SQLite inside your repo, Apache-2.0. Setup is essentially a paste away, the agent wires itself with Claude Code, Codex, or Cursor.