Skip to content

docs: spec how several agents share one repo without clobbering each other - #54

Merged
drknowhow merged 1 commit into
mainfrom
docs/agent-locks
Jul 30, 2026
Merged

docs: spec how several agents share one repo without clobbering each other#54
drknowhow merged 1 commit into
mainfrom
docs/agent-locks

Conversation

@drknowhow

Copy link
Copy Markdown
Owner

What

Design spec for Agent Locks — letting several agents work in one repo, or across several C3 projects, without clobbering each other. docs/agent-locks.md, 14 sections, house style matching docs/access-guard.md.

Design only. No code, no behaviour change. The status line in the doc says DRAFT, not frozen.

Why

Two gaps, both live today:

  • C3 has no cross-process coordination for source files. cli/tools/edit.py:20 guards same-file edits with a threading.Lock, but every Claude Code session spawns its own c3-mcp stdio server — so concurrent sessions can tear each other's writes. No mtime/hash staleness check exists either.
  • FleetDeck cannot cover it. Its PreToolUse hook matches Edit|Write|MultiEdit|NotebookEdit (fleetdeck/hook.py:23), while C3 blocks those tools and routes edits through mcp__c3__c3_edit. In a C3 project its collision gate never fires. The two systems currently cancel out.

What the spec settles

  • Two layers, not one. Torn writes (a cross-process file lock, ~15 LOC, no daemon) are a different problem from overlapping work (leases with TTL, intent, and fencing tokens). Conflating them is why this looks bigger than it is.
  • Lock state lives in the target project, not the caller's — c3_project(action='edit') writes across projects, and caller-scoped state would put the two agents in different files.
  • Adopt FleetDeck's key scheme and lifecycle verbatim (fleetdeck/paths.py, fleetdeck/locks.py) so both systems name the same file the same way.
  • The namespace trap. A per-call backend fallback — try the daemon, else go local — splits agents across two namespaces and collides them silently while the badge still reads protected. Backend is chosen per repo at config time; on failure mode decides open or closed, never a namespace switch.
  • C3 owns locks, FleetDeck owns the fleet. C3's state is files on disk with OS-level locking, so it works with no daemon; FleetDeck's engine fails open when its daemon is down. For a mutual-exclusion primitive that asymmetry decides it.

Coverage, stated honestly

Section 9 is a matrix. c3_shell running arbitrary code, non-Claude substrates, humans in editors, and repos without .c3/ are not covered, and the Hub badge must not pretend otherwise.

Phasing

The expensive part is gated on evidence, not opinion:

  1. _FileLock in edit.py — ~15 LOC, ships standalone
  2. FleetDeck two-line matcher fix — restores coverage today, starts logging denials
  3. Lease service + c3_locks + gates — ~400 LOC, gated on P2 denial data
  4. Hub tab + FleetDeck read integration — ~200 LOC

Tracked as C3 tasks 40e21392bbae, 5b0f7cee2dce, f9ee34510da8, 2a55f706d170 (tag agent-locks; dependencies wired P3 blocked by P1+P2, P4 blocked by P3).

Review focus

Section 10 (namespace trap) and Section 11 (C3 vs FleetDeck ownership) are the load-bearing decisions. Section 14 lists four open questions — implicit vs explicit lease acquisition is the one most likely to change the shape.

https://claude.ai/code/session_01Sbd9NbQfDcoJvvURD7KCVf

…other

C3 has no cross-process coordination for source files. cli/tools/edit.py:20
guards same-file edits with a threading.Lock, but every Claude Code session
spawns its own c3-mcp stdio server, so two sessions can tear each other's
writes -- and there is no staleness check anywhere in edit.py. Nothing today
stops two agents refactoring the same module for ten minutes.

FleetDeck (the standalone lock daemon) cannot cover this either: its
PreToolUse hook matches Edit|Write|MultiEdit|NotebookEdit, while C3 blocks
those tools and routes edits through mcp__c3__c3_edit. In a C3 project its
gate never fires, so the two systems currently cancel out.

This is the design, not the implementation. It separates torn writes (a
cross-process file lock, ~15 LOC, no daemon) from overlapping work (leases
with TTL and fencing tokens, adopting FleetDeck's semantics verbatim so both
systems name the same file the same way). It puts lock state in the target
project rather than the caller's, so cross-project writes coordinate. It
records the trap that a per-call backend fallback splits agents across two
namespaces and collides them silently while still badging the repo green.

Coverage is stated honestly: c3_shell running arbitrary code, non-Claude
substrates, humans in editors, and repos without .c3/ are not covered and the
UI must not pretend otherwise.

Phasing lets the empirical question decide the expensive part -- ship the
file lock and a two-line FleetDeck fix first, read the denial log, then judge
whether leases earn their ~600 LOC.

Claude-Session: https://claude.ai/code/session_01Sbd9NbQfDcoJvvURD7KCVf
@drknowhow
drknowhow merged commit d0d671b into main Jul 30, 2026
11 checks passed
drknowhow added a commit that referenced this pull request Jul 30, 2026
…off (#58)

Four merged PRs (#54-#57) had accumulated past the v2.64.0 tag, so the version
no longer identified the code: the installed build reported 2.64.0 while
running leases, a new tool, a new CLI command and a new Hub tab.

MINOR, not patch. I earlier called this 2.64.1; that was wrong. It adds
c3_locks, `c3 locks`, a Hub tab and a config section — new surface, not a fix.

What ships:

- Agent Locks. Two mechanisms for two different problems. A cross-process file
  lock stops two c3-mcp processes tearing each other's writes (and now covers
  create mode, which ran outside the lock entirely). Leases stop two agents
  doing overlapping work, carrying the intent from the edit summary so the
  blocked agent is told who holds the file and why. TTL is the real release
  mechanism, so a crashed agent cannot wedge a repo.
- c3_locks tool, `c3 locks` CLI with the human-only force-release, and a Hub
  Locks tab that badges an unreadable project UNREADABLE rather than showing
  it with zero leases.
- Access Guard built-in opt-out, two-key: a config entry AND a keyring
  attestation. Either alone leaves the built-in enforcing, so an agent that
  writes config.json cannot grant itself write access to ~/.claude/settings.json.
  The credential vault stays absolute.

The changelog states coverage honestly rather than implying it: leases gate
C3's own tool surfaces, and a raw c3_shell redirect, a non-Claude agent, or a
human in an editor is not covered.

1805 pass, ruff clean, both version constants bumped (test_version_sync guards
the pair).

Claude-Session: https://claude.ai/code/session_01Sbd9NbQfDcoJvvURD7KCVf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant