Skip to content

Multi Agent Write Protocol

Chris Sweet edited this page Jun 1, 2026 · 5 revisions

type: concept up: "Home_llm-wiki-memory-template" extends: "Three-Operations" status: aspirational tags: [protocol, multi-agent, concurrency, occ, collision-free, design, not-yet-implemented]

Multi-Agent Write Protocol

STATUS: design + prototype only. The protocol is specified here and exercised by a deterministic prototype (with structural CI coverage via the test harness), but is not yet wired into any agent overlay or shipped in the template. The template's current concurrency story is git pull --rebase at session boundaries; this protocol is the proposed evolution for projects with two or more agent writers. See Implementation-Status.

A write protocol for wikis that have multiple LLM-agent writers and (by assumption) no human writers. Prevents push-level collisions by construction: agents work directly on local main (standard git workflow), and a thin wiki-push wrapper handles conflict resolution only when an optimistic push is rejected. A complementary session_start hook keeps the agent's read view fresh.

Problem

The wiki is shared memory across a team. Multiple agent sessions (you-via-Claude in project A, your-collaborator-via-Claude in project B, a CI-bot agent running scheduled lints) all need to read and write the same git-backed wiki sub-repo. The existing template's defence is git pull --ff-only at SessionStart plus a pre-push collision guard: these resolve collisions when they happen, but agents still hit them and have to back out their work. We want a protocol that prevents collisions at the push level by construction, so that every successful write attempt produces a clean, atomic update to main.

The constraint that makes this tractable: only agents write to the wiki. No human bypasses the protocol with a manual edit. That means we can encode the protocol in the agent's write path and trust it to be followed.

Design at a glance

Concern Mechanism When it runs
Read freshness session_start (fetch + ff-pull + report incoming) Session start; agent's local main is brought up to date before reading
Write collision prevention wiki-push wrapper (optimistic push + merge-on-rejection + semantic resolve + retry) When the agent decides to push (default: not on every commit; only when user asks or agent explicitly proposes)
Mechanical merges .gitattributes merge=union for index_* and log_* Automatic during any git merge; no resolver involvement

The agent works on local main like any human would. The only thing that's not stock git is the semantic resolver — and that's the value we're adding regardless.

The protocol

Session start (read-side freshness)

When an agent session begins (Claude Code's SessionStart hook, or equivalent for other agents):

  1. git fetch origin on the wiki sub-repo.
  2. Compare local main to origin/main:
    • If local is at origin: report "up to date", continue.
    • If local is behind origin (fast-forward possible): git merge --ff-only origin/main; report N incoming commits with their authors and subjects so the agent's read context knows what changed.
    • If local is ahead of origin (un-pushed work): no pull needed; continue.
    • If local diverges from origin (un-pushed commits AND origin moved forward, or origin force-pushed): do NOT auto-rebase. Report the divergence and tell the agent / user how to inspect. The agent decides what to do (most likely: ask the human).

Pulling is fast-forward-only: the protocol never silently rewrites the agent's local history.

Write (collision-free push)

When the agent has commits on local main to publish (default: not on every commit; agent waits for user instruction or asks first):

  1. Optimistic push. Try git push origin main.
    • If accepted: done.
  2. Rejection → integrate. Push was rejected; another agent published in the meantime. Fetch origin, then git merge --no-commit --no-ff origin/main into local main.
    • If clean (no conflicts): commit the merge, goto 1.
  3. Conflict → classify and resolve. Read conflict markers; classify each file:
    • Index / log files (index_*.md, log_*.md): .gitattributes merge=union should have handled these mechanically during the merge. If we still see a conflict on them, apply a fallback union merge.
    • Substantive content overlap: invoke the semantic resolver (LLM in production, deterministic policy in tests). The resolver reads the conflict markers, decides drop / adapt / supersede, writes the resolved file.
    • Commit the resolved merge, goto 1.
  4. Retry cap. After K+1 total attempts (default K=3 retries, so 4 attempts total), the wrapper halts and surfaces the situation. Local main is left with the agent's un-pushed commits (and any partial merge commits from the failed retries) for the agent or user to inspect.

Properties:

  • Collision-free at the push level. Only one ref-update to origin/main ever succeeds at a time; losing pushes trigger a retry, not a stuck state.
  • No separate reconciler agent. Each writing agent reconciles its own attempts.
  • Atomic from main's perspective. main is never in a half-merged state; readers fetching main always see a consistent wiki.
  • Transparent to the agent. The agent works on local main like normal; no working-branch ceremony. The wrapper handles everything.
  • Standard git from the outside. Other tools (humans browsing GitHub, scripts that don't know about the protocol) see the wiki as a normal git repo with periodic merge commits.
  • No GitHub-side dependencies. The protocol uses only git mechanics; works on GitHub wikis (which don't support pull requests), private gitservers, or any other git remote.

Prior-art class: Optimistic Concurrency Control with semantic retry. Roots in Bayou (Xerox PARC, 1995) and the saga pattern's compensating actions. The novel application is the LLM agent as the retry's semantic resolver.

Default behaviour: don't push

The protocol replaces the mechanics of push, but not the policy. The existing convention applies: agents do not push by default; the human asks for a push, or the agent asks first. When push happens, the wrapper runs. "Push" is no longer literally git push — it's "invoke the wiki-push wrapper" — but from the agent's perspective the call shape is the same.

This protocol is for the wiki sub-repo only. Project main repos keep their existing conventions (commits land on branches; pushes are explicit). The wrapper is wiki-specific because the wiki is the only place multiple LLM agents routinely share write access.

Conflict classification

Conflict shape Classification Resolution
Index file (index_<repo>.md): both agents add new entries Mechanical Union merge via .gitattributes. Both entries present after merge; order is commit-order, not strict alpha.
Log file (log_<repo>.md): both agents append new entries Mechanical Union merge via .gitattributes. Order is commit-order; the Log-Entry-Attribution convention (one commit per entry) ensures entries don't interleave within a single commit.
Different pages, no shared edits None Three-way merge succeeds without intervention.
Same page, different sections Mechanical Three-way merge succeeds when sections are well-separated.
Same page, same section, non-overlapping lines Mechanical Three-way merge succeeds.
Same page, same section, overlapping lines Semantic Resolver reads both sides, decides drop / adapt / supersede, writes resolved content.
Both agents created the same new page Semantic Treated as full-overlap semantic conflict; one version becomes the base, the other agent's is folded in.

The .gitattributes configuration that drives the mechanical union merges:

index_*.md  merge=union
log_*.md    merge=union

This goes in the wiki sub-repo's .gitattributes at scaffolding time (likely written by init-wiki.sh in a production reference implementation). Without it, two agents adding new index entries produce a textual conflict that the resolver would mis-classify as semantic.

Required agent capabilities

The protocol assumes the agent can:

  • Run standard git operations: fetch, pull --ff-only, merge, push, read conflict markers, abort a merge.
  • Detect the four session_start outcomes (up-to-date / fast-forward / ahead / diverged) and respond appropriately (the divergent case requires the agent to ask the human, not auto-act).
  • For semantic resolution: read the other agent's contribution from conflict markers, understand the intent, decide drop / adapt / supersede, write the resolved content. This is the LLM-native capability that distinguishes this protocol from classical OCC-with-retry.

The agent does not need a separate reconciler, a coordinator service, a distributed lock manager, working-branch convention, or any infrastructure beyond git.

Failure modes and mitigations

  • Livelock. Agent A retries; agent C commits during the retry; A retries again; agent D commits; and so on. In principle possible; in practice, wiki edit frequency is low and sessions are bounded. Mitigation: the K=3 retry cap halts cleanly and surfaces the situation. If livelock becomes routine, optional backoff or a lease window can be layered on.
  • Network failure mid-push. Standard git semantics: the push either lands or it does not. Local main is unchanged on failure.
  • Stale local main. If the agent's session ran for hours and main has moved significantly, the conflict surface may be large. The protocol still works (rejected push triggers fetch + merge) but each retry takes longer. Mitigation: the agent can call session_start again mid-session to re-fetch, or simply rely on the push-time merge.
  • Divergent local main at session start. Agent's previous session left un-pushed work AND origin moved forward (someone else pushed) AND the histories differ. session_start returns the "diverged" code and does not auto-act. Agent should surface this to the human.
  • Two agents simultaneously create a page with the same name. One push wins. The loser sees the won page as a conflict on its next retry and reconciles semantically: usually by adopting the won content and folding its own contribution in.
  • Index entries pointing to dropped pages. If the semantic resolver drops a page that its own index entry referenced, the index entry becomes a dead link. The Verification-Gate catches this on the next ingest cycle.
  • Halt-at-cap recovery. When wiki-push halts at the retry cap, local main has the agent's un-pushed commits plus any partial merge commits from the failed retries. The wrapper leaves them for the agent or user to inspect. The recovery options are: keep retrying manually (git push); abandon (git reset to drop the un-pushed work); cherry-pick to a clean state.

Worked examples

Example 1: two agents add different new pages.

  • Agent A on machine M1: commits New-Topic-Alpha.md + index entry + log entry to local main. Pushes via wiki-push: accepted on first attempt.
  • Agent B on machine M2: commits New-Topic-Beta.md + index entry + log entry to local main. Pushes via wiki-push: rejected (A's commit is on origin). Fetches; merges A's commit into local main (clean: different file, plus .gitattributes union driver handles index and log). Commits the merge. Pushes: accepted.
  • Final state on main: both pages present; index has both entries; log has both entries in commit order.

Example 2: two agents edit different sections of the same page.

  • Agent A: edits "Method" section of Page-X.md. Commits, pushes: accepted.
  • Agent B: edits "Results" section of Page-X.md. Commits, pushes: rejected. Fetches; merges (clean: three-way merge handles non-overlapping section edits). Commits merge. Pushes: accepted.

Example 3: two agents edit the same section.

  • Agent A: rewrites the "Conclusion" of Page-X.md with a stronger framing. Pushes: accepted.
  • Agent B: rewrites the "Conclusion" of Page-X.md with a more cautious framing. Pushes: rejected. Fetches; merges; conflict on Page-X.md "Conclusion" section.
  • B's semantic resolver reads A's conclusion and chooses (most commonly) "adapt": integrates B's cautious framing into A's stronger framing (adds caveats, qualifies absolute claims). Commits resolved merge. Pushes: accepted.

Example 4: two agents both append to the log.

  • A appends ## [date] lint | ... to log_proto.md. Commits, pushes: accepted.
  • B appends ## [date] ingest | ... to log_proto.md. Commits, pushes: rejected. Fetches; merges. The .gitattributes union driver auto-merges the log (both entries appear in commit order). Commits the merge. Pushes: accepted.

Example 5: session_start with concurrent push.

  • Agent B's local main was at commit X (the seed). During the time B's session has been idle, agent A pushed commit Y to origin.
  • B's session_start runs: fetches origin (sees commit Y), detects local is behind, fast-forwards local main to Y, prints "pulled 1 incoming commit from origin/main" and the commit subject. B's read context now knows what A did.

Example 6: session_start on divergence.

  • Agent B's previous session left a local commit Z on top of X (un-pushed). Meanwhile, agent A pushed commit Y to origin. B's local has Z; origin has Y; both are on top of X. They've diverged.
  • B's session_start runs: fetches, detects divergence (neither is an ancestor of the other), does NOT auto-rebase, reports the divergence with inspection instructions. Local main remains at Z (B's un-pushed work is preserved). B (or the human) decides whether to push Z first, drop it, or manually merge.

Open questions

  • Order semantics for the log. Should the log file be strictly chronological, or is commit-order acceptable? Strict chronological requires the agent to re-sort after every union merge; commit-order is mechanical. Recommend commit-order with periodic agent-driven re-sort if drift becomes confusing.
  • Drop vs adapt vs supersede heuristics for the semantic resolver. When does each apply? Likely "adapt" is the default; "drop" only when the other agent literally did the same thing; "supersede" only with new information and an explicit rationale in the merge commit message.
  • Lease window opt-in. If livelock becomes routine, an optional lease mechanism could let one agent claim exclusive write access for N minutes. Specify the lease file convention if added later.
  • Push policy. Should pushes be agent-initiated (agent decides when its work is ready) or user-initiated (user explicitly asks for "push the wiki")? The convention so far: user-initiated; agent may ask. The protocol works either way.

Relationship to existing wiki pages

  • Verification-Gate: the four-criteria pre-commit gate still applies to the merge commit. The protocol is orthogonal to it.
  • Lesson-Validation-Methodology: structural validation of this protocol is what the prototype (linked from Implementation-Status) provides. Behavioural validation requires real LLM agents in the loop, which is deferred.
  • Sync-Flow: orthogonal. Sync-Flow governs how template improvements propagate to derived projects; this protocol governs how multiple agents share a single derived project's wiki.
  • PostToolUse-Hook: complementary. The PostToolUse hook nudges an agent to run the Verification Gate; the write protocol governs what happens when the agent actually pushes.
  • Lesson-Hook-Type-Matters: any agent implementation should follow the command-type-hook discipline (advisory, not blocking) for any reminders surfaced during the retry loop.

See also

  • Three-Operations: the wiki's read/write/lint operations; this protocol slots into the write side of ingest and lint.
  • Knowledge-Graph-Pipeline: orthogonal; the KG pipeline (also aspirational) consumes merged main state, regardless of how that state was produced.
  • Wiki-Corrections-Log: receipt for the 2026-06-01 redesign of this page.
  • Implementation-Status: tracks this page's aspirational status and links to the prototype.

Clone this wiki locally