DEV Community

Aviad Rozenhek
Aviad Rozenhek

Posted on

Zero-Conflict Architecture: The 80/20 of Parallel Development

How file-level ownership eliminated all merge conflicts (but not integration bugs)

Part 4 of the Multi-Agent Development Series

  • Part 1: Can 5 Claude Code Agents Work Independently?
  • Part 2: The Reality of "Autonomous" Multi-Agent Development
  • Part 3: Property-Based Testing with Hypothesis

TL;DR

We achieved 100% auto-merge success rate across 5 parallel PRs. Zero manual conflict resolution. The secret? File-level ownership - each PR creates NEW files or owns completely separate sections.

But here's the twist: Zero merge conflicts ≠ Zero integration work.

Integration still took 6 hours. 5 of those hours were AI debugging, not merging. File-level ownership solves the merge conflict problem, not the integration correctness problem.

The lesson: The 80/20 rule applies. 80% of parallel development success comes from one design decision (file ownership). The remaining 20% is the hard work (making code actually work together).


The Problem: Merge Conflicts Kill Parallel Work

Traditional parallel development:

Developer A: Modifies user_service.py lines 100-150
Developer B: Modifies user_service.py lines 120-180
Enter fullscreen mode Exit fullscreen mode

Result:

<<<<<<< HEAD (A's changes)
def get_user(user_id):
    # A's implementation
    return db.query(User).filter_by(id=user_id).first()
=======
def get_user(user_id):
    # B's implementation
    return cache.get(f"user:{user_id}") or db.query(User).get(user_id)
>>>>>>> feature/b (B's changes)
Enter fullscreen mode Exit fullscreen mode

Merge conflict! Someone (even AI) has to manually resolve it.


The Cost of Merge Conflicts

Time per conflict:

  • Understand both changes: 5-15 minutes
  • Resolve manually: 5-10 minutes
  • Test the merge: 5-10 minutes
  • Total: 15-35 minutes per conflict

In parallel development with 5 PRs:

  • Potential conflicts: 10-50 (depending on overlap)
  • Resolution time: 2.5 - 30 hours
  • Kills the benefit of parallel work!

Our experiment:

  • Expected conflicts (traditional approach): 10-20
  • Actual conflicts (zero-conflict architecture): 0
  • Time saved: ~10 hours

The Solution: File-Level Ownership

Core principle: Each PR owns completely separate files.

Design rules:

  1. CREATE new files instead of modifying shared files
  2. PARTITION existing files carefully (non-overlapping sections)
  3. DEFER shared changes to final integration PR

How We Applied It

PR What It Created What It Modified Conflict Risk
PR-1: Test Builders tests/test_video_moderation/test_builders.py (NEW) None ✅ Zero
PR-2: Budget Tests tests/test_video_moderation/unit/test_budget_allocation.py (NEW) video_moderator.py (production code) ✅ Zero (only PR touching this code)
PR-3: Segment Tests tests/test_video_moderation/unit/test_segment_duration.py (NEW) None ✅ Zero
PR-4: Property Tests tests/test_video_moderation/simulation/test_properties.py (NEW) None ✅ Zero
PR-5: Placement Tests tests/test_video_moderation/simulation/test_edge_cases.py (section) Only this file, only new section ✅ Zero
PR-6: Integration Cross-PR integration tests (NEW) Import statements (deferred to end) ⚠️ Medium (but sequential)

Key insight: File creation is conflict-free. Different files = different git objects = zero overlap.


The Design Patterns

Pattern 1: Create New Test Files

Instead of:

# Modifying existing test_video_moderator.py (shared by multiple PRs)
# PR-1 adds tests here
# PR-2 adds tests here
# PR-3 adds tests here
# → CONFLICT!
Enter fullscreen mode Exit fullscreen mode

Do this:

# PR-1 creates:
tests/test_video_moderation/test_builders.py

# PR-2 creates:
tests/test_video_moderation/unit/test_budget_allocation.py

# PR-3 creates:
tests/test_video_moderation/unit/test_segment_duration.py

# NO CONFLICTS - different files!
Enter fullscreen mode Exit fullscreen mode

Benefit:

  • Each PR owns its file completely
  • Modifications don't overlap
  • Git handles trivially (no merge needed, just add both files)

Pattern 2: Partition Existing Files by Section

When you MUST modify an existing file:

# test_edge_cases.py - Existing file

# Section 1: Staleness tests (existing)
class TestStalenessDetection:
    ...  # Don't touch these

# Section 2: Placement change tests (PR-5 OWNS this section)
class TestPlacementChanges:
    # PR-5 adds all tests here
    def test_placement_change_triggers_immediate_check(self):
        ...

    def test_stale_result_from_lower_risk_placement(self):
        ...

# No other PR modifies TestPlacementChanges → zero conflict
Enter fullscreen mode Exit fullscreen mode

Key: Clearly delineate sections. Use class-level organization.


Pattern 3: Defer Cross-Cutting Changes

Some changes affect ALL files (example: import statement updates).

Instead of having each PR update imports:

# PR-1 updates: from builders import MH, CH
# PR-2 updates: from builders import TestPolicy
# PR-3 updates: from builders import SAFE, UNSAFE
# → All touching same import lines → CONFLICT!
Enter fullscreen mode Exit fullscreen mode

Defer to integration PR:

# PR-1, PR-2, PR-3: Don't update imports in existing files

# PR-6 (integration): Update ALL imports at once
# No conflicts because it runs AFTER all PRs merged
Enter fullscreen mode Exit fullscreen mode

Tradeoff: Existing tests may use old helpers temporarily. But zero conflicts.


The Results: 100% Auto-Merge

Merge Timeline

Time Event Conflicts Resolution Time
Hour 8 Merge PR-1 (test builders) 0 0 minutes ✅
Hour 12 Merge PR-2 (budget tests) 0 0 minutes ✅
Hour 16 Merge PR-3 (segment tests) 0 0 minutes ✅
Hour 20 Merge PR-5 (placement tests) 0 0 minutes ✅
Hour 24 Merge PR-4 (property tests) 0 0 minutes ✅
Total 5 PRs merged 0 0 minutes

100% auto-merge success rate!


Git Operations

Typical merge:

$ git checkout integration-branch
$ git pull origin pr-2-branch
Auto-merging complete.
Fast-forward merge (no conflicts).
  tests/test_video_moderation/unit/test_budget_allocation.py | 145 +++++++
  1 file changed, 145 insertions(+)
  create mode 100644 tests/test_video_moderation/unit/test_budget_allocation.py

$ git push
Done!
Enter fullscreen mode Exit fullscreen mode

Every single merge looked like this. No conflict markers. No manual resolution. Just git doing its job.


What Zero-Conflict Doesn't Solve

Here's where it gets interesting.

Total integration time: 6 hours

  • Merge operations: 30 minutes (git commands, pushing)
  • Debugging: 5 hours (making code actually work)

Integration still involved:

1. API Mismatches (2 hours)

Problem: Agents assumed APIs that don't exist.

Example:

# PR-1 assumed:
result.details.classification == VideoModerationClassification.SAFE

# Reality:
result.details.is_appropriate == True  # Different field name!
Enter fullscreen mode Exit fullscreen mode

Impact: 13 tests failed due to field name mismatches.

Fix: Human investigated actual model structure, corrected tests.

Lesson: File-level ownership prevents merge conflicts, but doesn't verify API correctness.


2. Cross-PR Integration Issues (1 hour)

Problem: PR-4 changed tier ordering (fairness-first), broke PR-2's tier priority tests.

Example:

# PR-2 tested (before PR-4):
def test_tier1_beats_tier2():
    """Tier 1 (staleness) should beat Tier 2 (fairness)"""
    # Passed with original tier ordering

# After PR-4 merged (fairness-first):
# Same test now fails!
# Tier 2 (fairness) now HIGHER priority than Tier 1 (staleness)
Enter fullscreen mode Exit fullscreen mode

Impact: 3 tier priority tests failed after PR-4 integration.

Fix: Updated tests to match new tier ordering.

Lesson: Zero merge conflicts doesn't mean zero integration testing.


3. Missing Features (30 minutes)

Problem: PR-3 wrote tests for features never implemented.

Example:

# PR-3 tested:
assert result.recommended_segment_duration_seconds == 3.0

# Reality: Field doesn't exist in model!
Enter fullscreen mode Exit fullscreen mode

Impact: 4 tests skipped (feature not implemented).

Fix: Marked tests as skipped with clear reason.

Lesson: Integration still needs to verify feature completeness.


4. Test Isolation Issues (1.5 hours)

Problem: Property tests (PR-4) occasionally failed due to shared state.

Example:

# test_properties.py uses hypothesis random generation
# Sometimes generated data conflicted with test database state
# Flaky failures: passed locally, failed in CI
Enter fullscreen mode Exit fullscreen mode

Impact: 5 property tests showing intermittent failures.

Fix: Improved test isolation (separate database, better cleanup).

Lesson: Parallel test development still needs integration testing for isolation.


The 80/20 Breakdown

80% of integration success came from:

  1. File-level ownership (zero merge conflicts)
  2. Centralized test builders (shared infrastructure early)
  3. Clear PR scope (each PR knew exactly what it owned)

20% of effort spent on:

  1. Fixing API mismatches (13 failures)
  2. Communication overhead (FEEDBACK files)
  3. Progress tracking (status documents)
  4. Cross-PR integration (tier ordering conflicts)

Key insight: Architecture solved the easy problem (conflicts). Humans still needed to solve the hard problem (correctness).


The Real Cost of Integration

Activity Time % of Total
Git operations (fetch, merge, push) 30 min 8%
Running tests (pytest execution) 30 min 8%
Debugging API mismatches 120 min 33%
Fixing cross-PR issues 60 min 17%
Verifying feature completeness 30 min 8%
Test isolation debugging 90 min 25%
Total 360 min (6 hours) 100%

Merge conflicts: 0% of integration time (architecture win!)
Debugging: 83% of integration time (hard work)

The lesson: Zero-conflict architecture is necessary but not sufficient. The hard work is making code work together, not merging cleanly.


Design Patterns for Zero-Conflict Architecture

Pattern 1: Feature Flags Over Shared Changes

Problem: Multiple PRs need to modify shared configuration.

Traditional approach:

# config.py (shared file)
# PR-1 adds: ENABLE_FEATURE_A = True
# PR-2 adds: ENABLE_FEATURE_B = True
# PR-3 adds: ENABLE_FEATURE_C = True
# → CONFLICT on same file!
Enter fullscreen mode Exit fullscreen mode

Zero-conflict approach:

# PR-1 creates: features/feature_a.py
ENABLE_FEATURE_A = True

# PR-2 creates: features/feature_b.py
ENABLE_FEATURE_B = True

# PR-3 creates: features/feature_c.py
ENABLE_FEATURE_C = True

# config.py (modified by integration PR only):
from features import feature_a, feature_b, feature_c
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Subdirectory Per PR

Organize by work stream:

tests/test_video_moderation/
├── unit/
│   ├── test_budget_allocation.py      # PR-2 owns this
│   └── test_segment_duration.py       # PR-3 owns this
├── simulation/
│   ├── test_properties.py             # PR-4 owns this
│   └── test_edge_cases.py             # PR-5 owns section
├── test_builders.py                   # PR-1 owns this
└── integration/
    └── test_cross_pr_integration.py   # PR-6 owns this
Enter fullscreen mode Exit fullscreen mode

Benefit: Clear ownership, easy to see what each PR touched.


Pattern 3: Interface Files Over Implementation Sharing

Problem: Multiple PRs need shared test utilities.

Conflict-prone:

# test_utils.py (everyone modifies this)
# PR-1 adds: def make_participant(...)
# PR-2 adds: def make_policy(...)
# PR-3 adds: def make_result(...)
# → CONFLICTS!
Enter fullscreen mode Exit fullscreen mode

Zero-conflict:

# PR-1 creates: test_builders.py with ALL builders
# Other PRs IMPORT from test_builders, don't modify it
# If they need new builders, they ADD to their own files
# Integration PR consolidates duplicates later
Enter fullscreen mode Exit fullscreen mode

Tradeoff: Temporary duplication OK, consolidate in integration.


Pattern 4: Versioned Shared State

Problem: Multiple PRs need test database setup.

Conflict-prone:

# conftest.py (shared pytest fixtures)
# Everyone adds their fixture here
# → CONFLICTS!
Enter fullscreen mode Exit fullscreen mode

Zero-conflict:

# conftest.py (stays minimal, provides base fixtures)
@pytest.fixture
def base_db():
    ...

# PR-2 creates: conftest_budget.py
@pytest.fixture
def budget_test_db(base_db):
    # Extend base for budget tests
    ...

# PR-3 creates: conftest_segment.py
@pytest.fixture
def segment_test_db(base_db):
    # Extend base for segment tests
    ...

# Integration: Move fixtures back to main conftest if widely used
Enter fullscreen mode Exit fullscreen mode

When Zero-Conflict Architecture Breaks Down

Zero-conflict works best for:

  • ✅ New feature development (create new files)
  • ✅ Test additions (new test files)
  • ✅ Independent modules (low coupling)
  • ✅ Parallel refactoring (different areas)

Zero-conflict struggles with:

  • ❌ Cross-cutting concerns (logging, error handling)
  • ❌ Shared interfaces (API contracts that all code uses)
  • ❌ Database schema changes (migrations affect all code)
  • ❌ Dependency updates (everyone uses same libraries)

For these cases:

  • Serialize the work (one PR handles it)
  • OR use feature flags/versioning
  • OR accept conflicts and plan for resolution

Scaling: How Many Parallel PRs?

Our experiment: 5 parallel PRs

Could we do more?

PR Count Conflict Probability Coordination Overhead Sweet Spot?
2-3 PRs Very low Low ✅ Yes - easy to manage
4-6 PRs Low (with good design) Medium ✅ Yes - our experiment worked well
7-10 PRs Medium High 🟡 Maybe - depends on isolation
11+ PRs High Very high ❌ Likely not worth it

Factors:

  1. Communication overhead scales with N² (every PR might affect every other)
  2. Integration complexity grows exponentially with PR count
  3. Human orchestration becomes bottleneck beyond 5-7 PRs

Sweet spot: 4-6 parallel PRs for most projects.


Recommendations for Practitioners

✅ Do This

1. Plan file ownership upfront

  • Before starting, map out which PR owns which files
  • Visualize with a table (like we did above)
  • Get everyone to agree on boundaries

2. Create > Modify

  • Strongly prefer creating new files over modifying existing
  • Even if it means temporary duplication
  • Consolidation in integration PR is cheap

3. Use clear naming conventions

  • test_<feature>_<pr_number>.py makes ownership obvious
  • Subdirectories per work stream
  • Comments at top: "// OWNED BY PR-2, DO NOT MODIFY"

4. Defer cross-cutting changes

  • Import updates → integration PR
  • Shared config → integration PR
  • Database migrations → separate PR (serialized)

5. Test integration continuously

  • Integration PR merges frequently (not just at end)
  • Run full test suite after each merge
  • Catch API mismatches early

❌ Don't Do This

1. Assume zero conflicts means zero work

  • Budget for integration debugging time (5-10 hours for 5 PRs)
  • Plan for API verification
  • Expect cross-PR issues

2. Over-partition

  • Don't create 50 tiny PRs just to avoid conflicts
  • Sweet spot is 4-6 meaningful work streams
  • Coordination overhead grows with PR count

3. Skip planning

  • "We'll figure out ownership as we go" → conflicts
  • Plan upfront which files each PR owns
  • Communicate boundaries clearly

4. Neglect integration testing

  • Zero merge conflicts ≠ code works
  • Still need comprehensive integration tests
  • Budget time for cross-PR verification

The Alternative: Traditional Merge-Heavy Workflow

For comparison, what would traditional approach look like?

Traditional workflow:

  1. 5 developers work in parallel
  2. Each modifies shared files (test_video_moderator.py, conftest.py)
  3. PR-1 merges → conflict-free ✅
  4. PR-2 merges → 3 conflicts (vs PR-1)
  5. PR-3 merges → 5 conflicts (vs PR-1, PR-2)
  6. PR-4 merges → 7 conflicts (vs PR-1, PR-2, PR-3)
  7. PR-5 merges → 10 conflicts (vs all others)
  8. Total: 25 conflicts, ~10 hours resolving

Our approach:

  1. 5 PRs work in parallel
  2. Each owns separate files
  3. PR-1 merges → auto ✅
  4. PR-2 merges → auto ✅
  5. PR-3 merges → auto ✅
  6. PR-4 merges → auto ✅
  7. PR-5 merges → auto ✅
  8. Total: 0 conflicts, ~0.5 hours merging (git operations)

Time saved on merging: ~9.5 hours
Time spent on integration debugging: ~5 hours
Net savings: ~4.5 hours (50% reduction)

But more importantly: Parallel work was actually possible. Traditional approach would have serialized after conflicts mounted.


Real-World Examples

Example 1: Microservices Architecture

Scenario: 5 teams building microservices

Zero-conflict approach:

services/
├── user-service/     # Team A owns
├── payment-service/  # Team B owns
├── order-service/    # Team C owns
├── notify-service/   # Team D owns
└── analytics-service # Team E owns
Enter fullscreen mode Exit fullscreen mode

Each team:

  • Owns their directory completely
  • No modifications to other services
  • Shared interfaces versioned (v1, v2 APIs)

Result: True parallel development, zero conflicts


Example 2: Frontend Component Library

Scenario: 5 developers building React components

Zero-conflict approach:

components/
├── Button/           # Dev A
│   ├── Button.tsx
│   └── Button.test.tsx
├── Input/            # Dev B
│   ├── Input.tsx
│   └── Input.test.tsx
├── Modal/            # Dev C
├── Dropdown/         # Dev D
└── Table/            # Dev E
Enter fullscreen mode Exit fullscreen mode

Shared changes:

// Integration PR updates index.ts:
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';
// ... (deferred to integration)
Enter fullscreen mode Exit fullscreen mode

Result: 5 components developed in parallel, merged without conflicts


Conclusion

What we proved:

  • ✅ File-level ownership eliminates merge conflicts (100% auto-merge)
  • ✅ Parallel development is possible with good architecture
  • ✅ 75% time savings despite orchestration overhead

What we learned:

  • ❌ Zero merge conflicts ≠ zero integration work
  • ⚠️ Integration debugging still takes significant time (5 hours)
  • ⚠️ Architecture solves conflicts, humans solve correctness

The 80/20 insight:

  • 80% of success: File-level ownership (one design decision)
  • 20% of effort: Everything else (but the hard work)

Would we do it again? Absolutely! Eliminating merge conflicts removed a huge coordination bottleneck.

Next time we'd improve:

  1. Better API verification upfront (model introspection)
  2. Clearer communication protocol (automated status updates)
  3. Pre-planned integration test strategy

What's Next

In the following articles:

  • Article 5: Communication Protocols for AI Agents That Can't Talk

    • How we iterated 4 times to get file-based messaging working
    • What worked and what didn't
  • Article 6: The Budget Calculator Paradox

    • Flip-flopping 8 times on a simple formula
    • Build the calculator first, use it everywhere

Tags: #parallel-development #git #merge-conflicts #architecture #software-engineering #team-collaboration #zero-conflict


This is Part 4 of the Multi-Agent Development Series.

Discussion: How do you handle merge conflicts in your team? Have you tried file-level ownership? What's your sweet spot for parallel PR count? Share in the comments!

Top comments (0)