---
title: "Atomic Multi-File Edits for AI Coding Agents"
canonical: https://wavect.io/blog/atomic-multi-file-edits-ai-coding-agents/
language: en
description: "A Semaprax lesson on atomic multi-file edits: immutable generations, one active pointer, failure boundaries and a practical buyer checklist."
image: "https://wavect.io/img/blog/headers/header_atomic-multi-file-edits-ai-coding-agents.png"
---

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

8 min read · 6 Sep 2026 Last reviewed September 6, 2026

[**Next**](/blog/semantic-identity-rust-agent-edits/)

# Atomic Multi-File Edits for AI Coding Agents: The Semaprax Lesson

TL;DR

A multi-file AI edit is not atomic merely because each file is replaced atomically or the final result becomes one Git commit. A reader can still observe a mixture while files change one by one. During Semaprax development, we addressed that bounded problem with immutable source generations and one authenticated ACTIVE pointer: preflight the whole proposal, build and verify the complete candidate, then switch the pointer once. Cooperating readers see the old or new managed snapshot. The protocol does not make raw files, Git, editors, network filesystems or power-loss recovery atomic. Teams buying or building agent tooling should ask what readers are protected, where the commit authority lives, how stale proposals fail, and what happens before and after the publication pivot.

**If an AI coding agent changes several dependent files, replacing each file safely is not enough. The project needs one publication boundary.** Otherwise a build process, language server, test watcher or second agent can read half of the old program and half of the new one.

We met this problem while developing [Semaprax](/semaprax/), our experimental agent-native systems language. The useful lesson is broader than the language: prepare a complete immutable generation, verify it, then switch one small active pointer. This article explains the pattern, its limits and the questions a technical buyer should ask before trusting any claim of “atomic” agent edits.

## What is an atomic multi-file edit?

**An atomic multi-file edit makes one complete old state or one complete new state visible to its defined readers, never an in-between mixture.** The phrase is incomplete until the system names those readers, the files in scope, the commit point and the failure model.

Consider a two-file change that renames an exported function and updates its caller. If the definition changes first, a watcher can observe a caller for the old name with a definition for the new name. If the caller changes first, it sees the opposite mismatch. Both files may be valid text. The combined program is not valid at that instant.

## The learning: publish generations, not a sequence of writes

Semaprax initially needed a bounded way to publish verified changes across several source files. Its architecture decision rejects sequential replacement because a reader can observe a mixed generation. The accepted design keeps complete source generations immutable and selects one through a single `ACTIVE` record. The [pinned Semaprax architecture decision](https://github.com/wavect/semaprax/blob/942ed70388e2b40f4ee98ea5dd5e0c193fa98f4d/docs/decisions/0002-managed-workspace-generations.md) records both the decision and the rejected alternatives.

```
.semaprax-workspace/
  ACTIVE
  generations/
    <old-workspace-revision>/
    <candidate-workspace-revision>/
```

The write path has five distinct stages:

1. **Bind the proposal to a base revision.** Reject it if the selected workspace has moved.
2. **Preflight every changed file.** Resolve all operations against one authenticated base before writing a candidate.
3. **Build the whole candidate separately.** Include unchanged files too, so the candidate is a complete generation rather than a bag of patches.
4. **Verify the complete candidate.** Check formats, identities, limits, digests and the invariants the protocol actually claims.
5. **Switch one pointer.** After final rechecks, replace only `ACTIVE`. A cooperating reader resolves that pointer and reads one immutable generation.

## Why is one atomic file rename not enough?

Writing a temporary file and renaming it over the destination is a valuable single-file pattern. Rust documents `std::fs::rename` as one rename operation and also lists platform-specific behavior and cross-mount failure. It does not offer a portable transaction over an arbitrary set of paths. See the [Rust standard library rename contract](https://doc.rust-lang.org/std/fs/fn.rename.html).

Repeating that operation for five files creates five publication moments. If a reader can run between moments two and three, the overall program is still exposed as a mixture. Atomicity at the file level does not automatically compose into atomicity at the project level.

## Doesn’t Git already make multi-file changes atomic?

**A Git commit identifies one complete tree, but that does not make every live working-tree transition atomic to editors, watchers or other processes.** The distinction is between publishing repository history and changing the files that active tools read.

Git itself illustrates how precise the boundary must be. `git update-ref` can verify an expected old object ID and queue ref changes in a transaction. Its documentation also warns that a concurrent reader may still see a subset of multiple ref modifications even though individual refs are updated atomically. The [official Git update-ref documentation](https://git-scm.com/docs/git-update-ref) is a useful model for expected-version checks and explicit transaction states.

Use branches, worktrees and commits for collaboration, review and recovery. Add a managed publication layer only when running consumers must observe one coherent application snapshot during a change.

## Why immutable generations are a practical pattern

Immutable generations move most risk before the commit point. A candidate can be built, inspected and rejected without modifying the selected state. The final operation is small because it changes a pointer, not every payload file.

This is not unique to agent tooling. Nix explains atomic upgrades in similar terms: packages are not overwritten in place, and a profile moves to a new generation, avoiding a window with some old and some new files. The [official Nix architecture guide](https://nixos.org/guides/how-nix-works/) provides a mature example of the broader generation pattern.

The agent-specific addition is evidence. A fluent model response should not be the commit authority. The system should preserve the base revision, proposed operations, candidate digest, validation results and final pivot outcome so another component can check what happened.

## What Semaprax actually implements, and what it does not

At the audited commit, Semaprax defines a bounded transaction for 2 to 16 managed `.spx` files. Writers take an exclusive lock, construct or authenticate a complete candidate generation, perform final checks and replace `ACTIVE`. Cooperating readers take a shared lock and resolve the selected immutable generation. The [pinned workspace transaction specification](https://github.com/wavect/semaprax/blob/942ed70388e2b40f4ee98ea5dd5e0c193fa98f4d/docs/SEMANTIC-WORKSPACE-TRANSACTION-V1.md) defines the wire format, limits, diagnostics, evidence and nonclaims.

The boundary matters more than the headline. The protocol does not make raw source paths, Git, editors or noncooperating readers atomic. It does not claim network filesystem behavior, power-loss durability, automatic rollback, general repository semantics or arbitrary multi-file repair. Semaprax remains pre-alpha research, and this article does not turn its bounded evidence into a production-readiness claim.

## Build or buy: eight questions for agent tooling vendors

1. **Who are the protected readers?** Ask whether the guarantee covers only the tool’s API, the working tree, language servers, builds and external processes.
2. **What is the base revision?** Every proposal needs an expected version and a clear stale-rejection path.
3. **What is the commit point?** “We use temp files” is not an answer for a multi-file change.
4. **Is validation run on the complete candidate?** Per-file syntax checks miss cross-file breakage.
5. **Who owns write authority?** Model output, review evidence and approval tokens should not silently become reusable commit power.
6. **What happens before and after the pivot?** Pre-pivot rejection and post-pivot ambiguity require different recovery procedures.
7. **Which durability claim is tested?** Process crash, operating-system crash, power loss and network storage are different failure models.
8. **Can you reproduce the evidence?** Ask for hostile tests, fixed fixtures, limits and exact versions, not only a polished demo.

## When do you need this architecture?

You probably do not need a managed generation protocol for an agent that proposes a patch, stops and waits for a human to review a normal Git diff. You should consider it when autonomous work changes several coupled files while builds, services or other agents read the workspace continuously, and a mixed state can trigger deployment, code generation, migration or an irreversible action.

Start one layer earlier if the agent cannot identify the right program entities. Our [Semaprax engineering note on semantic identity](/blog/semantic-identity-rust-agent-edits/) explains why stable declaration IDs and revision-bound patches are safer than stale line offsets. The [AI agent harness guide](/blog/agent-harness-engineering/) places publication inside the larger context, policy, tool, verification and observability system.

## Frequently asked questions

### Are Git commits atomic?

A commit names one complete repository tree. That is not the same as guaranteeing that live readers of a changing working directory never observe intermediate file states.

### Is atomic write the same as rollback?

No. Atomic publication defines what becomes visible at the pivot. Rollback, cleanup and recovery after a crash are separate contracts and need separate evidence.

### Do immutable generations prevent merge conflicts?

No. They control publication visibility for cooperating readers. Branch coordination, semantic conflicts and human review remain separate concerns.

### What is the minimum useful design?

Define the protected readers, bind the proposal to a base revision, validate a complete candidate outside the active state and make one small authenticated switch. Then document everything the switch does not guarantee.

*Editorial disclosure: OpenAI Codex assisted with research, drafting and translation. Wavect checked the technical claims against Semaprax commit `942ed70` and the linked primary documentation on 6 September 2026. No performance or production-readiness claim is inferred from model output.*

## Final thoughts

The most important word in an atomic-edit claim is not atomic. It is scope. A safe design says who reads, what version the proposal targets, which state is verified and exactly where publication happens.

Semaprax taught us to stop treating several safe file writes as one safe program change. Build the complete generation first, verify it, then switch one pointer. Keep the nonclaims as explicit as the happy path.

## You may also like..

[**Why Agent Edits Need Semantic Identity** See how stable declaration IDs and revision-bound semantic patches constrain the edit before publication.](/blog/semantic-identity-rust-agent-edits/) [**AI Enablement vs Generic AI Consulting** Compare implementation-led agent engineering with a strategy-only engagement.](/compare/ai-enablement-vs-generic-ai-consultancy/)

Agent engineering

## Continue through this cluster

Coding agents, MCP, context systems, evaluation and the controls required for dependable automation.

[Start with the cornerstone**Graph Engineering for AI Agents: When Does a Knowledge Graph Pay Off?**](/blog/graph-engineering-ai-agents/)

- [AI Agent Knowledge Transfer: Use Frontier Models Once, Then Scale Cheaper](/blog/agent-knowledge-transfer-cheaper-models/)
- [claude-rotate: One Proxy for Multiple Claude Max Accounts](/blog/claude-rotate-multi-account-proxy/)
- [Feynman Review: Is This Open-Source AI Research Agent Ready for Teams?](/blog/feynman-open-source-ai-research-agent/)
- [Obscura Browser Review: Claims, Limits and Production Fit](/blog/obscura-rust-browser-ai-agents/)
- [Claude Code Design System: 4 Parts for On-Brand UI](/blog/claude-code-design-system-files/)

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

8 min read · 6 Sep 2026 Last reviewed September 6, 2026

[**Next**](/blog/semantic-identity-rust-agent-edits/)

## Structured Data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://wavect.io/#organization",
      "@type": [
        "Organization",
        "ProfessionalService",
        "LocalBusiness"
      ],
      "employee": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "founder": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "legalRepresentative": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "name": "Wavect GmbH",
      "subjectOf": {
        "@id": "https://wavect.io/verified-claims.json#dataset",
        "@type": "Dataset",
        "creator": {
          "@id": "https://wavect.io/#organization",
          "@type": [
            "Organization",
            "ProfessionalService",
            "LocalBusiness"
          ]
        },
        "description": "A machine-readable registry of quantitative and qualitative claims published by Wavect, with review dates, localized page appearances and public third-party citations where available.",
        "inLanguage": "en",
        "isAccessibleForFree": true,
        "license": "https://creativecommons.org/licenses/by/4.0/",
        "name": "Wavect verified publication claims",
        "url": "https://wavect.io/verified-claims.json"
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/team/kevin-riedl/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Kevin Riedl",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796365",
        "https://www.linkedin.com/in/wsdt",
        "https://github.com/wsdt"
      ],
      "url": "https://wavect.io/team/kevin-riedl/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/team/christof-jori/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Christof Jori",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796367",
        "https://www.linkedin.com/in/jocr77/",
        "https://github.com/jo-chris"
      ],
      "url": "https://wavect.io/team/christof-jori/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/#website",
      "@type": "WebSite",
      "inLanguage": [
        "en",
        "de",
        "es",
        "zh"
      ],
      "name": "Wavect",
      "potentialAction": {
        "@type": "SearchAction",
        "query-input": "required name=search_term_string",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://wavect.io/search/?q={search_term_string}"
        }
      },
      "publisher": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/blog/atomic-multi-file-edits-ai-coding-agents/#webpage",
      "@type": "WebPage",
      "dateModified": "2026-09-06",
      "inLanguage": "en",
      "isPartOf": {
        "@id": "https://wavect.io/#website",
        "@type": "WebSite"
      },
      "lastReviewed": "2026-09-06",
      "url": "https://wavect.io/blog/atomic-multi-file-edits-ai-coding-agents/"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "abstract": "A multi-file AI edit is not atomic merely because each file is replaced atomically or the final result becomes one Git commit. A reader can still observe a mixture while files change one by one. During Semaprax development, we addressed that bounded problem with immutable source generations and one authenticated ACTIVE pointer: preflight the whole proposal, build and verify the complete candidate, then switch the pointer once. Cooperating readers see the old or new managed snapshot. The protocol does not make raw files, Git, editors, network filesystems or power-loss recovery atomic. Teams buying or building agent tooling should ask what readers are protected, where the commit authority lives, how stale proposals fail, and what happens before and after the publication pivot.",
  "articleBody": " Blog overview/AI and agents/Agent engineering Atomic Multi-File Edits for AI Coding Agents: The Semaprax Lesson TL;DR A multi-file AI edit is not atomic merely because each file is replaced atomically or the final result becomes one Git commit. A reader can still observe a mixture while files change one by one. During Semaprax development, we addressed that bounded problem with immutable source generations and one authenticated ACTIVE pointer: preflight the whole proposal, build and verify the complete candidate, then switch the pointer once. Cooperating readers see the old or new managed snapshot. The protocol does not make raw files, Git, editors, network filesystems or power-loss recovery atomic. Teams buying or building agent tooling should ask what readers are protected, where the commit authority lives, how stale proposals fail, and what happens before and after the publication pivot. If an AI coding agent changes several dependent files, replacing each file safely is not enough. The project needs one publication boundary. Otherwise a build process, language server, test watcher or second agent can read half of the old program and half of the new one. We met this problem while developing Semaprax, our experimental agent-native systems language. The useful lesson is broader than the language: prepare a complete immutable generation, verify it, then switch one small active pointer. This article explains the pattern, its limits and the questions a technical buyer should ask before trusting any claim of “atomic” agent edits. What is an atomic multi-file edit? An atomic multi-file edit makes one complete old state or one complete new state visible to its defined readers, never an in-between mixture. The phrase is incomplete until the system names those readers, the files in scope, the commit point and the failure model. Consider a two-file change that renames an exported function and updates its caller. If the definition changes first, a watcher can observe a caller for the old name with a definition for the new name. If the caller changes first, it sees the opposite mismatch. Both files may be valid text. The combined program is not valid at that instant. The learning: publish generations, not a sequence of writes Semaprax initially needed a bounded way to publish verified changes across several source files. Its architecture decision rejects sequential replacement because a reader can observe a mixed generation. The accepted design keeps complete source generations immutable and selects one through a single ACTIVE record. The pinned Semaprax architecture decision records both the decision and the rejected alternatives. .semaprax-workspace/ ACTIVE generations/ <old-workspace-revision>/ <candidate-workspace-revision>/ The write path has five distinct stages: Bind the proposal to a base revision. Reject it if the selected workspace has moved. Preflight every changed file. Resolve all operations against one authenticated base before writing a candidate. Build the whole candidate separately. Include unchanged files too, so the candidate is a complete generation rather than a bag of patches. Verify the complete candidate. Check formats, identities, limits, digests and the invariants the protocol actually claims. Switch one pointer. After final rechecks, replace only ACTIVE. A cooperating reader resolves that pointer and reads one immutable generation. Why is one atomic file rename not enough? Writing a temporary file and renaming it over the destination is a valuable single-file pattern. Rust documents std::fs::rename as one rename operation and also lists platform-specific behavior and cross-mount failure. It does not offer a portable transaction over an arbitrary set of paths. See the Rust standard library rename contract. Repeating that operation for five files creates five publication moments. If a reader can run between moments two and three, the overall program is still exposed as a mixture. Atomicity at the file level does not automatically compose into atomicity at the project level. Doesn’t Git already make multi-file changes atomic? A Git commit identifies one complete tree, but that does not make every live working-tree transition atomic to editors, watchers or other processes. The distinction is between publishing repository history and changing the files that active tools read. Git itself illustrates how precise the boundary must be. git update-ref can verify an expected old object ID and queue ref changes in a transaction. Its documentation also warns that a concurrent reader may still see a subset of multiple ref modifications even though individual refs are updated atomically. The official Git update-ref documentation is a useful model for expected-version checks and explicit transaction states. Use branches, worktrees and commits for collaboration, review and recovery. Add a managed publication layer only when running consumers must observe one coherent application snapshot during a change.",
  "articleSection": "Engineering",
  "author": {
    "@id": "https://wavect.io/team/kevin-riedl/#person",
    "@type": "Person",
    "name": "Kevin Riedl",
    "sameAs": [
      "https://www.wikidata.org/wiki/Q139796365",
      "https://www.linkedin.com/in/wsdt",
      "https://github.com/wsdt"
    ],
    "url": "https://wavect.io/team/kevin-riedl/"
  },
  "citation": [
    {
      "@type": "WebPage",
      "name": "pinned Semaprax architecture decision",
      "url": "https://github.com/wavect/semaprax/blob/942ed70388e2b40f4ee98ea5dd5e0c193fa98f4d/docs/decisions/0002-managed-workspace-generations.md"
    },
    {
      "@type": "WebPage",
      "name": "Rust standard library rename contract",
      "url": "https://doc.rust-lang.org/std/fs/fn.rename.html"
    },
    {
      "@type": "WebPage",
      "name": "official Git update-ref documentation",
      "url": "https://git-scm.com/docs/git-update-ref"
    },
    {
      "@type": "WebPage",
      "name": "official Nix architecture guide",
      "url": "https://nixos.org/guides/how-nix-works/"
    },
    {
      "@type": "WebPage",
      "name": "pinned workspace transaction specification",
      "url": "https://github.com/wavect/semaprax/blob/942ed70388e2b40f4ee98ea5dd5e0c193fa98f4d/docs/SEMANTIC-WORKSPACE-TRANSACTION-V1.md"
    }
  ],
  "dateModified": "2026-09-06",
  "datePublished": "2026-09-06",
  "description": "A multi-file AI edit is not atomic merely because each file is replaced atomically or the final result becomes one Git commit. A reader can still observe a mixture while files change one by one. During Semaprax development, we addressed that bounded problem with immutable source generations and one authenticated ACTIVE pointer: preflight the whole proposal, build and verify the complete candidate, then switch the pointer once. Cooperating readers see the old or new managed snapshot. The protocol does not make raw files, Git, editors, network filesystems or power-loss recovery atomic. Teams buying or building agent tooling should ask what readers are protected, where the commit authority lives, how stale proposals fail, and what happens before and after the publication pivot.",
  "headline": "Atomic Multi-File Edits for AI Coding Agents: The Semaprax Lesson",
  "image": "https://wavect.io/img/blog/headers/header_atomic-multi-file-edits-ai-coding-agents.svg",
  "inLanguage": "en",
  "keywords": "AI coding agents, Software architecture",
  "mainEntityOfPage": {
    "@id": "https://wavect.io/blog/atomic-multi-file-edits-ai-coding-agents/",
    "@type": "WebPage"
  },
  "publisher": {
    "@id": "https://wavect.io/#organization",
    "@type": [
      "Organization",
      "ProfessionalService",
      "LocalBusiness"
    ]
  },
  "url": "https://wavect.io/blog/atomic-multi-file-edits-ai-coding-agents/",
  "wordCount": 1814
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "item": "https://wavect.io/",
      "name": "Home",
      "position": 1
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/overview/",
      "name": "Blog overview",
      "position": 2
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/topics/ai-agents/",
      "name": "AI and agents",
      "position": 3
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/clusters/agent-engineering/",
      "name": "Agent engineering",
      "position": 4
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/atomic-multi-file-edits-ai-coding-agents/",
      "name": "Atomic Multi-File Edits for AI Coding Agents",
      "position": 5
    }
  ]
}
```
