Skip to content

Instantly share code, notes, and snippets.

@simbo1905
Last active August 22, 2026 23:43
Show Gist options
  • Select an option

  • Save simbo1905/25596aca12cf0057d01ded9dcc0853a9 to your computer and use it in GitHub Desktop.

Select an option

Save simbo1905/25596aca12cf0057d01ded9dcc0853a9 to your computer and use it in GitHub Desktop.
Lock Folder Util - Atomic mkdir-based mutex for agent swarms sharing one resource (gadget, robot arm, coffee machine). Zero dependencies, full audit log, self-healing stale locks.

🔒 Lock Folder Util — The Mutex Your Agent Swarm Desperately Needs

Twelve Agents. One Resource. Zero Supervision. What Could Possibly Go Wrong?

You know that moment when you finally parallelise your agent fleet and watch with pride as twelve subagents sprint off to do the work of a whole team... straight into the same shared resource. Maybe it is the one licensed tool seat. Maybe it is the office coffee machine you told everyone could "handle concurrent requests". Maybe you set a dozen agents loose to find the funniest cat picture on the internet and they all decide the single shared webcam is the fastest route to greatness, and now your laptop is taking twelve selfies of the ceiling while a queue of increasingly confused agents interrogates it about cats.

Yeah. We've all been there.

The Problem

  • 🤯 Multiple agents driving one shared resource (a robot, a tool seat, one very confused peripheral)
  • 💥 Interleaved commands corrupting each other's work mid-flight
  • 🕵️ No audit trail of who held the resource and when
  • ⚰️ A crashed agent leaving the resource "locked" forever (or worse, not locked at all)

The Solution: lock_folder_util.py

A zero-dependency Python script that turns one humble mkdir into a fully auditable mutual-exclusion lock. Because mkdir is atomic at the filesystem level: it either creates the directory or fails because it already exists. No races. No lock file content. No NFS weirdness. Just one empty directory standing guard.

# The entire synchronization mechanism:
mkdir .tmp/lock.lock    # succeeds exactly once
rmdir .tmp/lock.lock    # release

That's it. That's the lock.


🚀 Features That Actually Matter

1. The run Form (Use This One)

Acquire, execute, release, no matter what:

./lock_folder_util.py run --slug agent-7 -- your-command --with args

Crashes mid-command? The lock goes stale. The next agent breaks it after --stale seconds and carries on. Self-healing, zero babysitting.

2. Full Audit Trail

Every acquire, release, stale-break, and timeout is one line in .tmp/lock.log with a UTC timestamp and the agent's slug:

2026-08-22T23:05:52Z agent-1 acquired
2026-08-22T23:05:53Z agent-1 released
2026-08-22T23:05:58Z agent-2 acquired
2026-08-22T23:05:59Z agent-2 released
2026-08-22T23:06:04Z agent-3 acquired
2026-08-22T23:06:05Z agent-3 released
2026-08-22T23:06:10Z agent-4 acquired
2026-08-22T23:06:11Z agent-4 released
2026-08-22T23:06:16Z agent-5 acquired
2026-08-22T23:06:16Z agent-5 released
2026-08-22T23:06:22Z agent-6 acquired
2026-08-22T23:06:23Z agent-6 released
2026-08-22T23:06:28Z agent-7 acquired
2026-08-22T23:06:29Z agent-7 released
2026-08-22T23:06:34Z agent-8 acquired
2026-08-22T23:06:35Z agent-8 released

Eight agents. Sixteen lines. Perfectly interleaved acquire/release pairs. Not one overlap. When something does go wrong at 2am, you will know exactly who was holding the door.

3. Stale-Lock Breaking (Self-Healing)

Agent dies mid-critical-section? Its lock sits there with a birth timestamp. The next agent in line checks the age, finds it past --stale (default 600s), removes it, logs the break, and proceeds. No human intervention. No queue frozen for eternity.

4. Zero Dependencies

Python 3 standard library. That's the whole list. No pip, no cargo, no apt-get. If your box runs Python, you have a mutex.

5. Works Everywhere mkdir Works

macOS, Linux, WSL, the BSDs, that NAS in the cupboard. If the filesystem can atomically create a directory, this lock holds.

6. Configurable Lock Directory, Fail Fast

The lock directory defaults to .tmp and is overridden with the env var LOCK_FOLDER_DIR (for tests, or for co-located projects that want an isolated lock). At startup the script fails fast unless the directory exists, is a directory, and is writable. A misconfigured lock is a noisy error message at second zero, not a silent no-op mutex that lets your agents pile into the coffee machine:

LOCK_FOLDER_DIR=/tmp/swarm-test ./lock_folder_util.py run --slug t1 -- sleep 1
# lock_folder_util: lock dir /nonexistent is not a writable directory; ...

📖 Pseudo Man Page (The Details)

NAME

lock_folder_util.py — mutual exclusion for agents sharing one resource, via an atomic mkdir

SYNOPSIS

./lock_folder_util.py acquire --slug NAME [--poll 15] [--stale 600] [--timeout 1800]
./lock_folder_util.py release --slug NAME
./lock_folder_util.py run --slug NAME [--poll 15] [--stale 600] -- <command...>

OPTIONS

--slug NAME    Your agent's identity, logged with every transition
--poll N       Seconds between lock attempts while waiting (default 15)
--stale N      Break locks older than N seconds (default 600)
--timeout N    Give up after N seconds total (default 1800)

THE PROTOCOL (read this bit)

  1. If you are the batch's first agent, take the lock immediately.
  2. Everyone else: sleep 60 once, then poll every 15 seconds.
  3. Wrap the ENTIRE resource-driving critical section, and release immediately after your last resource command. Thinking, reading, and file work need no lock.
  4. Never put files inside the lock directory. It must stay empty or rmdir fails and you have made a new problem.

EXIT STATUS

  • 0: Success (or the wrapped command's own status, in run mode)
  • 1: Timed out waiting, or bad usage

EXAMPLES

Example 1: Reserve the shared gadget for a job

./lock_folder_util.py run --slug gadget-keeper -- \
    ./use_the_gadget.py --mode serious

Example 2: Manual acquire around a long session

./lock_folder_util.py acquire --slug worker-1
# ... command the shared resource, poke it carefully ...
./lock_folder_util.py release --slug worker-1

Example 3: Compressed timings for a fast swarm

./lock_folder_util.py run --slug agent-9 --poll 2 --stale 12 -- sleep 1

🧪 Testing Suite Included

The full methodology, reproducible in under a minute. Compress the timings so the lock storm actually shows the failure mode if one exists: initial sleep 6s (not 60s), poll 2s (not 15s), critical section 1s, and an 8-agent thundering herd:

# One agent takes the lock immediately, seven sleep 6s then storm it
./lock_folder_util.py run --slug agent-1 --poll 2 --stale 12 -- sleep 1 &
for i in 2 3 4 5 6 7 8; do
  ( sleep 6; ./lock_folder_util.py run --slug agent-$i --poll 2 --stale 12 -- sleep 1 ) &
done
wait
cat .tmp/lock.log

Then verify the invariants mechanically, not by eyeball:

python3 - <<'EOF'
lines = open('.tmp/lock.log').read().splitlines()
held = 0; overlaps = 0
for l in lines:
    if l.endswith('acquired'): held += 1; overlaps = max(overlaps, held)
    elif l.endswith('released'): held -= 1
print('PASS: no overlapping critical sections' if overlaps <= 1
      else f'FAIL: {overlaps} agents held the lock simultaneously')
import os
print('PASS: no lock leaked' if not os.path.isdir('.tmp/lock.lock')
      else 'FAIL: lock leaked')
EOF

What you want to see:

  • acquired=N released=N gave_up=0 stale_breaks=0 (all agents got through)
  • Overlap check: PASS (never two holders)
  • Leak check: PASS (lock directory gone at the end)

And a stale-lock test, for the crash case: create the lock, backdate its mtime with os.utime, and watch the next agent break it cleanly and log the break.

One footnote from the trenches: do NOT write the staleness check in bash with stat -f %m. On a box where GNU coreutils stat is first on PATH, -f means filesystem status, not birth time, and your age check becomes a word salad of apfs superblock stats. The Python helper exists precisely so no agent ever has to learn this the noisy way.


⚡ Installation

Star then download. Star. "⭐💫🌟" You know, like thumbs up, but for yoof of today. STAR THE GIST ⭐⭐⭐

If you use gh cli, and you should, then you can get it with this fancy one-liner:

for f in $(gh gist view 25596aca12cf0057d01ded9dcc0853a9 --files); do gh gist view 25596aca12cf0057d01ded9dcc0853a9 -f "$f" > "$f"; done && chmod +x lock_folder_util.py

If you do not use gh, well, srsly, do. Or if you must do it manually its over here:

[https://gist.github.com/simbo1905/25596aca12cf0057d01ded9dcc0853a9]

Make executable

chmod +x lock_folder_util.py

Optional: Add to PATH

cp lock_folder_util.py ~/bin/lock_folder_util.py

Or just run it directly if your not the global-install-files sort:

./lock_folder_util.py run --slug you -- echo "hello exclusive world"


💡 Use Cases That'll Make You Look Like a Genius

For Agent Fleet Wranglers

  • One gadget, twelve agents: serialise every command, poke, and reading without a coordinator process
  • RAG pipeline guard: one writer process for the index, many researchers in flight
  • Serialised device access: one robot arm, one 3D printer, one oscilloscope, N impatient agents

For Anyone With a Shared Toy

  • The one licensed EDA tool seat everyone "just quickly needs"
  • The dev database that only tolerates one migration at a time
  • The communal coffee machine, the office 3D printer, the single shared webcam being interrogated by twelve cat-picture agents
  • The family TV remote (results may vary)

🎯 Why This Exists

Born from the exact scenario above: a fleet of agents, one shared resource, and a first attempt at "polite staggering" that told the last agent to sleep 44 minutes before touching the thing. Forty. Four. Minutes. Of sleeping. While holding a todo list.

Sometimes you just need the dumbest possible thing that works: one empty directory, mkdir, and a log file. As that is obviously how you think and act. You are not an orchestrator up at 2am using SCREAMING ALL CAPS as your agents deadlock over the office coffee machine. That is definately not you, no. Me neither.


📜 License

MIT or Public Domain. Use it, abuse it, put it in production, whatever. No warranty implied. If two agents somehow end up on the coffee machine at once, check the log before checking your assumptions.


Made with ❤️ and one atomic mkdir by someone who once watched an agent sleep 44 minutes.

Now go lock your shared resources like a pro. 🔒✨

#!/usr/bin/env python3
"""Serialise a shared resource across parallel agents with an mkdir lock.
One resource, many agents: every use of the resource must hold the lock.
The lock is an empty directory created atomically with os.mkdir, which
fails if it already exists. Acquisition order is nondeterministic but
mutual exclusion is guaranteed. The lock must never be given children;
release is always rmdir.
Usage:
lock_folder_util.py acquire --slug worker-1 [--poll 15] [--stale 600] [--timeout 1800]
lock_folder_util.py release --slug worker-1
lock_folder_util.py run --slug worker-1 [--poll 15] [--stale 600] -- <command...>
acquire: take the lock or poll until free; break locks older than --stale
seconds (a crashed holder); give up after --timeout seconds. Every
transition is logged as one line (UTC timestamp, slug, event) to
<lockdir>.log next to the lock, so contention is auditable after the fact.
run: acquire, exec the command, always release, exit with its status. This
is the form agents should use: a crash between acquire and release leaves a
stale lock that the next agent breaks after --stale seconds.
Lock directory: defaults to .tmp under the current directory and is
overridden by the env var LOCK_FOLDER_DIR (absolute, or relative to the
current directory). The script fails fast at startup unless the lock
directory exists, is a directory, and is writable. The env override exists
for testing and for co-located projects that want an isolated lock;
agents doing real work should leave the default in place so every user
contends on the one lock.
Sizing guidance from the 2026-08-22 batch runs: typical agent critical
sections run 3 to 5 minutes, so the default stale break is 600 seconds
(generous but bounded); poll every 15 seconds with one initial 60 second
sleep so a burst of simultaneous agents does not thrash the check. Keep
the critical section as short a burst as possible: do all reading,
planning, and file patching outside the lock and hold it only for the
resource commands themselves.
"""
import argparse
import os
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
LOCK_DIR = Path(os.environ.get("LOCK_FOLDER_DIR", ".tmp"))
LOCK = LOCK_DIR / "lock.lock"
LOG = LOCK_DIR / "lock.log"
if not (LOCK_DIR.is_dir() and os.access(LOCK_DIR, os.W_OK)):
sys.exit(
f"lock_folder_util: lock dir {LOCK_DIR} is not a writable directory; "
"set LOCK_FOLDER_DIR or create it first"
)
def _log(slug: str, msg: str) -> None:
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
with LOG.open("a", encoding="utf-8") as f:
f.write(f"{ts} {slug} {msg}\n")
def _lock_age() -> float:
return time.time() - LOCK.stat().st_mtime
def acquire(slug: str, poll: float, stale: float, timeout: float) -> None:
deadline = time.monotonic() + timeout
while True:
try:
os.mkdir(LOCK)
_log(slug, "acquired")
return
except FileExistsError:
pass
try:
age = _lock_age()
if age > stale:
try:
os.rmdir(LOCK)
_log(slug, f"broke stale lock (age {age:.0f}s)")
continue
except OSError:
pass
except FileNotFoundError:
pass
if time.monotonic() > deadline:
_log(slug, "gave up")
sys.exit(f"lock_folder_util: timed out after {timeout}s waiting for {LOCK}")
time.sleep(poll)
def release(slug: str) -> None:
try:
os.rmdir(LOCK)
_log(slug, "released")
except FileNotFoundError:
_log(slug, "release: lock already gone")
def main() -> None:
p = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
sub = p.add_subparsers(dest="cmd", required=True)
a = sub.add_parser("acquire")
a.add_argument("--slug", required=True)
a.add_argument("--poll", type=float, default=15.0)
a.add_argument("--stale", type=float, default=600.0)
a.add_argument("--timeout", type=float, default=1800.0)
r = sub.add_parser("release")
r.add_argument("--slug", required=True)
run = sub.add_parser("run")
run.add_argument("--slug", required=True)
run.add_argument("--poll", type=float, default=15.0)
run.add_argument("--stale", type=float, default=600.0)
run.add_argument("--timeout", type=float, default=1800.0)
run.add_argument("command", nargs=argparse.REMAINDER)
args = p.parse_args()
if args.cmd == "release":
release(args.slug)
return
acquire(args.slug, args.poll, args.stale, args.timeout)
if args.cmd == "acquire":
return
command = list(args.command)
if command and command[0] == "--":
command = command[1:]
if not command:
release(args.slug)
sys.exit("lock_folder_util: run: no command given")
try:
rc = subprocess.call(command)
finally:
release(args.slug)
sys.exit(rc)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment