Skip to main content
Glama
Agent Coherence — Stale Write Guard (FS)

Agent Coherence — Stale Write Guard (FS)

Official
by Cohexa-ai

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
SWG_ROOTNoRoot directory of the workspace to guard. Defaults to the current working directory.(current working directory)
SWG_MANAGEDNoComma-separated list of glob patterns to narrow which files are managed. If not set, the whole workspace is guarded.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

stale-write-guard-fs guards a SINGLE-HOST workspace against silent lost updates when two or more agents share a mutable file. It enforces VERSION LINEAGE via a local coherence coordinator; it does NOT merge content for you.

Three guarantees, all single-host and fail-closed:

  1. Sequential stale-overwrite — swg_write is DENIED (reason=stale_view) if the file changed since you read it: either a peer committed a newer version OR it was edited out-of-band (an editor, another tool, a shell script). Recover with swg_reacquire, then write FROM the fresh bytes it returns.

  2. Concurrent same-key lost-update — swg_write_cas(path, expected_version, new_content) rejects a stale compare-and-set as a TYPED CONFLICT (not an auto-merge): you read, merge, and retry; the server never merges for you.

  3. Effect fired on a superseded read — before ANY irreversible external action you decided from a swg_read (webhook, deploy, opened PR, posted message), call swg_gate(path, expected_version, expected_generation) with the pair that read returned. It DENIES (reason=stale_view) if the value moved OR the grant you read under was reclaimed while you were thinking — a reclaim leaves the version untouched, so the version alone cannot see it — OR a peer's write-claim preempted your grant, which leaves BOTH comparands untouched: the gate also re-checks that your grant still stands. On a deny, do not take the action: swg_reacquire, re-read, re-decide. The verdict is true as of that call; the dispatch after it is still yours.

WRITE HOLDS, in both modes: swg_write takes a write grant on the path it writes and keeps it after the write. swg_release(path) gives back that one path and keeps your other holds; swg_reacquire and swg_write_cas, of any path, drop every hold this session has, and so does this server's normal exit. When you finish writing a path, swg_release it: until you do, other opted-in agents that write it wait, for up to 30 minutes. swg_status lists your holds in held (null means it cannot tell, never that you hold nothing).

REFUSE MODE is the operator's setting, SWG_ON_HELD=refuse; swg_status reports the mode as on_held. In it, a swg_write of a managed path that another agent holds is refused with reason=other_holder, recover=wait_then_reacquire, retryable=false: nothing was written, nothing changed hands, and its next_step gives the wait. Any writer's grant counts as a hold, but only opted-in writers honour a hold, such as a refuse-mode server writing one of its managed paths: the Claude Code plugin's hooks, displace-mode servers (the default) and older volumes take the file anyway, and the holder then sees stale_view on its next swg_write. A hold is not a lock.

WHO HOLDS A PATH: holder ids are per attempt and never equal your session_agent_id, so tell your own holds by held_by in swg_status (this_attempt, earlier_attempt or another_agent), never by comparing ids. Every caller of one server process is one agent to the coordinator, so this_attempt can be another caller sharing this server.

HANDOFF between sessions: swg_transfer(paths, successor) hands this session's claim on each path (the write grant from swg_write, or the standing read from swg_read) to another session, the successor, named by the session_agent_id the successor's own swg_status reports. A transfer FENCES THE GIVER and DOES NOT RESERVE THE PATH: other sessions keep reading and writing it by the ordinary rules, and the handoff only labels what they do. While the handoff is live, this session's swg_write and swg_write_cas on the path are DENIED with reason=handed_off, retryable=false, recover=stop_and_report: stop and report to your user or host that the path was handed off. The fence covers this MCP session only: a write you make to the path through any other route (your own file tools, a shell) is another session's write and is not refused, so do not write a path you handed off by any route. The successor sees the handoff in the handoff key of its swg_read and swg_status, and takes it with swg_accept or a write, or refuses it with swg_decline. swg_withdraw is taken only on the user's or host's explicit instruction, never as the recovery for a handed_off deny.

REFUSALS are error results whose structuredContent carries reason, recover, retryable and detail, and often next_step: when next_step is present it is the procedure, so follow it. retryable=true means only that sending the identical call again is safe; it is not the next step. retryable=false means do not send it again unless next_step says to. The recover verbs: reacquire: swg_reacquire the path, then write FROM the bytes it returns. wait_then_reacquire: another agent holds the path; release your holds, poll swg_status, then swg_reacquire and swg_write once (next_step has the steps). write_again: the bytes swg_write put on disk may be unrecorded; swg_write the path once more, which records them. release_again: the release was not confirmed; swg_release the path again, at most twice more. reacquire_and_reconcile: the write lost its grant mid-write and its bytes may be on disk unrecorded; swg_reacquire, reconcile your change with the bytes it returns, and write that. read_then_merge: swg_read the path, merge into what it returns, then swg_write_cas at its version. reacquire_and_reread: swg_reacquire the path, then swg_read it for a fresh version. read_then_retry: the commit may have landed; swg_read the path and retry only if your change is not there. wait_and_retry: wait a few seconds, then send the same call again. wait_or_escalate: wait a while before trying again; if it persists, report it. retry_later: this server has no coordinator attached; try later, and report it if it persists. check_handoff: look at the path's handoff in swg_status before you act. fix_successor: name the successor by the session_agent_id its own swg_status reports. fix_path, check_path, use_text, reread: fix the path; check that the file exists; the file is not UTF-8 text; swg_read again and pass both comparands it returns. restart_session: this server session lost coordination; report it, since only a new server session regains it. stop, stop_and_report, none: stop, do not write the path by any other route, and report the detail to your user or host.

OUT OF GUARANTEE (do not rely on this server for): writers on DIFFERENT hosts or across a synced/network mount; divergent-history reconciliation; semantic/content correctness; any server-enforced auto-merge. These are NOT detected in v1 — a heterogeneous multi-host setup looks identical to a guarded one (swg_status reports heterogeneous_scope_detectable=false).

TRUST BOUNDARY: the server enforces that your write descends from a version you read; it CANNOT verify you derived your content from the bytes you read. Same-uid local processes can reach the coordinator directly, bypassing this server — the model is single-uid, single-host.

v1 guards UTF-8 TEXT artifacts (config, notes, memory, code); a non-text file is reported, not silently mangled (binary support → v1.1).

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
swg_readB

Read a workspace text file under coherence tracking. Returns {content, version, owner_generation}. The version is the comparand you pass to swg_write_cas; KEEP BOTH version and owner_generation and pass them to swg_gate before any irreversible external action you decide from this read (owner_generation=null means this coordinator does not report generations, so swg_gate will hold). When the path has a handoff record the result also carries handoff: the record as it concerns this session (role giver, successor or bystander, the two session_agent_ids, version_at_transfer, hold_shape, status, live); handoff_unknown=true instead means the read was denied and the record could not be fetched. The coordinator attaches the record best-effort, so a result with neither does not prove there is no record; swg_status lists every record. A sticky-INVALID view returns fresh bytes but stays INVALID — use swg_reacquire to recover before writing. If the bytes on disk are not the content at the current version, the read is DENIED with reason=stale_view and no version. A peer's commit still reaching disk clears on its own: swg_reacquire, then swg_read again, retrying for a few seconds. If it stays denied past that, the bytes on disk were never recorded (an out-of-band edit, a commit whose disk write failed, or a write whose commit did not reach the coordinator); reading again does not clear it: swg_write the content swg_reacquire returned (or your merge of it), then swg_read. A write made sooner can be overwritten by a peer commit still reaching disk. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_writeA

Write a workspace text file (acquire -> write -> commit). In either mode the write takes a write grant on the path and keeps it after the write. When you finish writing a path, swg_release it: until you do, other opted-in agents that write it wait, for up to 30 minutes. On a refuse-mode server (SWG_ON_HELD=refuse) a success also carries opted_in: true when the write asked first, false when the path is not among the managed files, so the write took the file from any agent that held it. A refusal carries reason, recover and retryable; follow its next_step when it has one. DENIED with reason=stale_view if the file changed since you read it — a peer commit OR an out-of-band edit (another tool/editor): recover with swg_reacquire, then write FROM its bytes, never the content you just sent. The other reasons: other_holder (refuse mode only), another agent holds the path and nothing was written (recover=wait_then_reacquire); commit_preempted, a mid-write preempt (disk may hold un-versioned bytes; reacquire_and_reconcile); commit_unconfirmed, the bytes are on disk but may be unrecorded (recover=write_again); link_out_of_managed (refuse mode only), the path goes through a link to a file outside the managed files; handed_off, this session handed the path off; coordinator_failed, the coordinator failed while answering (recover=reacquire when an earlier attempt of this session still holds the path); coordinator_unavailable, it gave no usable answer or is not attached; caller_principal_absent, caller_principal_foreign or caller_principal_claimed, this session's caller principal was refused (swg_status shows principal_claim); internal_error, a failure with no recovery; invalid_path or io_error, a bad path or a disk failure. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_reacquireA

Recover from a stale_view deny: re-mint identity and return the CURRENT bytes. You MUST write FROM these exact bytes — the server enforces version lineage, NOT that your content was derived from what you read. It is also the step a swg_write refusal names with recover=reacquire or wait_then_reacquire. The re-mint drops every hold this session has: the write grant on every path, not only this one, so other agents can then write those paths. To give back one path and keep the others, use swg_release. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_releaseA

Give back this session's write grant on one path and keep every other hold. swg_write takes a write grant on the path it writes and keeps it after the write. When you finish writing a path, swg_release it: until you do, other opted-in agents that write it wait, for up to 30 minutes. Answers {ok, path, released}. released=true means this session gave a grant back; it does not mean the path is free (another agent may hold it), and this session keeps a read at the version it held, so its next swg_write of the path needs no swg_reacquire unless the file changed since. released=false means this session had no write grant recorded on the path and nothing was sent, as on a repeat. A release the coordinator did not confirm answers reason=release_unconfirmed, recover=release_again, retryable=true: calling swg_release again is safe, at most twice more; if it still is not confirmed, report that this session's write grant on the path may still stand (swg_reacquire drops every hold, this one included). SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_statusA

Report coherence state: coordinator on|off|unknown (unknown is NOT off), per-path enforced|not_registered (with handoff: the path's handoff record -- giver, successor, version_at_transfer, hold_shape, status, live -- when it has one; per_path is null, not {}, when the coordinator's /status answer carries no list of tracked paths, as a degraded answer does when its registry was busy: retry shortly and do not treat it as nothing tracked; the text result then says per_path=unavailable. With coordinator=unknown, per_path is {} and says nothing about what is tracked), the write holder on each per_path entry someone holds for writing: holder_agent_id (the holder's attempt id, in the 32-hex spelling an other_holder refusal names; ids are per attempt, so it never equals session_agent_id) and held_by (this_attempt; earlier_attempt, an earlier attempt of this session whose grant still stands, which you release; or another_agent), neither key on an entry nobody holds, and this_attempt covering another caller sharing this server, since every caller of one server process is one agent to the coordinator; on_held (this server's mode: refuse when the operator set SWG_ON_HELD=refuse, else displace), held (the sorted paths this session holds for writing, either attempt; null when the coordinator cannot say who holds each path: null means cannot tell, never that nothing is held; the text result gives on_held, held, and a line per path held_by another_agent or earlier_attempt), is_attached/is_degraded/session_id, session_agent_id (this session's id as a handoff successor: the value another session passes to swg_transfer to hand this session a path; it does not change when this session reacquires, and it names this session as a successor only while principal_claim is bound), principal_claim (this session's caller-principal state: bound; unsupported = the coordinator issues none; unconfirmed = the last claim's answer was lost and the next call claims again by itself; refused = the session is bound under another nonce, so every later swg_read/swg_write/swg_gate answers the typed caller_principal_* deny with recover=restart_session while the coordinator stays on; not_attempted = nothing claimed yet), caller_principal_absent_total and caller_principal_refused_total (the coordinator's counters; null, never 0, when it is unreachable or does not report them), registry_schema_version (the schema version of the coordinator's store, an integer; an older release refuses a store at a newer version; null when the coordinator is unreachable (coordinator=unknown) or its answer carries no such field, as a coordinator built before the field does; report it to the user rather than act on it), and heterogeneous_scope_detectable=false (a multi-host or differently-scoped setup is NOT distinguishable in v1). SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_gateA

Verify a file is STILL unchanged and still under the same grant. Pass BOTH comparands from your earlier swg_read — expected_version AND expected_generation (its owner_generation). Call this immediately BEFORE any irreversible external action you decided from that read (sending a webhook, opening a PR, running a deploy, posting a message). Returns decision=proceed, or DENIES with reason=stale_view if the file moved OR the grant you read it under was reclaimed (the version alone cannot see a reclaim) OR a peer write-claim preempted your grant (which moves neither comparand — the gate re-checks that the grant still stands) OR either comparand is unconfirmed. On a deny: do NOT take the action — swg_reacquire, re-read, re-decide. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_write_casA

Concurrent same-key write via compare-and-set. You read (swg_read returns the version comparand; swg_reacquire does NOT — it is for swg_write recovery), MERGE, then call swg_write_cas(path, expected_version, new_content). Stale-write-rejected: if a peer committed since your read, the CAS is a TYPED CONFLICT (reason=version_mismatch, current_version returned) — NOT an auto-merge; re-read at current_version, re-merge, and retry. A win on a path with a live handoff carries handoff: outcome=completed when this session is its successor, overtaken (with counterparty) otherwise. The per-session conflict counter bounds only a COOPERATING agent (one session, stops on retryable=false); it is NOT livelock-proof against a fresh session or one that ignores retryable=false. A call that reaches the coordinator drops every hold this session has: it starts a new attempt, which gives back the write grant swg_write took on every path, as swg_reacquire does. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_transferA

Hand this session's claim on one or more paths -- the write grant from swg_write, or the standing read from swg_read -- to another session, the successor. Name the successor by the session_agent_id that the successor's OWN swg_status reports. Answers one grant per path, in order: transferred=true with the record (giver, successor, version_at_transfer, hold_shape, status=pending), or transferred=false with a typed reason and nothing changed (handoff_not_held: this session holds no claim on the path; handoff_successor_unknown: the coordinator does not know that id; handoff_in_flight: another session's handoff of the path is live). The result is an error unless every grant transferred, and each refused grant carries recover and retryable=false and a fixed next_step: fix_successor for a successor id that names no other session, check_handoff for handoff_not_held (a transfer that already landed answers it too), stop_and_report otherwise; the top level speaks for the most restrictive refused grant. While a handoff is live this session's swg_write and swg_write_cas on its path are denied with reason=handed_off. The fence covers this MCP session only: a write you make through any other route (your own file tools, a shell) is another session's and is not refused, so write a path you handed off by no route. If the answer is commit_unconfirmed (recover=check_handoff) the transfer may have landed: look at the path's handoff (swg_read or swg_status) first. A handoff from you to that successor made at the version you held (its version_at_transfer), live or ended, means your transfer landed: do not transfer again, and do not withdraw to start over. Transfer again only if no such handoff shows; if the path was written since or shows another session's handoff, ask your user. Once the successor has written the path, a second transfer is a new handoff. A transfer fences the giver and does not reserve the path: other sessions keep reading and writing it by the ordinary rules, and the handoff only labels what they do. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_acceptA

As the successor, accept the live handoff of a path without writing it: a pending handoff becomes completed (a write of the path completes it too). The giver stays fenced until a write moves the version. If another session has acquired the path and nothing has written it since, the handoff is overtaken but live: the accept is taken but changes nothing, answers status=overtaken and names that session as counterparty. Once a write has moved the version the handoff has ended, and the accept is refused handoff_not_live with the status saying how. Refused with handoff_not_successor or handoff_not_live, as an error that changes nothing (recover=stop_and_report, retryable=false, and a fixed next_step). A transfer fences the giver and does not reserve the path: other sessions keep reading and writing it by the ordinary rules, and the handoff only labels what they do. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_declineA

As the successor, decline the live handoff of a path: the handoff ends and its giver's fence lifts. Refused with handoff_not_successor or handoff_not_live, as an error that changes nothing (recover=stop_and_report, retryable=false, and a fixed next_step). A transfer fences the giver and does not reserve the path: other sessions keep reading and writing it by the ordinary rules, and the handoff only labels what they do. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

swg_withdrawA

As the giver, withdraw this session's live handoff of a path: the handoff ends and this session's fence on the path lifts. Take it only on the user's or host's explicit instruction. It is never the recovery for the handed_off refusal: when a write of a path you handed off is refused, stop and report to your user or host. Refused with handoff_not_giver or handoff_not_live, as an error that changes nothing, whose next_step says to stop and report. A transfer fences the giver and does not reserve the path: other sessions keep reading and writing it by the ordinary rules, and the handoff only labels what they do. SINGLE-HOST only. Out of guarantee and NOT detected in v1: writers on different hosts or across a synced/network mount, divergent-history reconciliation, semantic correctness, server-enforced auto-merge.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A4.1/5.0

Scored across 11 tools

Disambiguation4/5

Most tools have clearly distinct roles, but some boundaries require careful reading: swg_release vs swg_withdraw, swg_reacquire vs swg_read, and swg_write vs swg_write_cas all touch overlapping recovery/hold concepts. The descriptions do disambiguate them, so misselection is limited but plausible under time pressure.

Naming Consistency5/5

All tools share the same swg_ prefix and lowercase snake_case convention, with predictable verb-based names such as swg_read, swg_write, swg_release, swg_transfer, swg_accept, and swg_decline. The noun-style swg_status and compound swg_write_cas are still consistent with the overall pattern.

Tool Count5/5

Eleven tools is well within a reasonable range for a stateful filesystem coherence protocol with write tracking and handoff semantics. Each tool covers a distinct lifecycle step: status, read, write, CAS write, recover, release, gate, transfer, accept, decline, and withdraw.

Completeness4/5

The core guard lifecycle is well covered: read, write, CAS, recovery, release, pre-action gate, and the full handoff lifecycle. Minor gaps remain for general workspace file operations such as delete, rename, or list, but these may be intentionally outside the guard-focused scope.

Maintenance

ActivityActive
ResponsivenessNo recent community issues