docs/design/agent-proximity.md
Status: v0 implemented in
scripts/lib/agent-proximity/. This is the moat layer of ECC 2.0 — spatial deconfliction for multiple agents (and humans) working the same codebase, modeled on aircraft collision avoidance (TCAS).
Two aircraft sharing airspace don't wait until they touch — TCAS continuously measures their separation and closure rate, issues a Traffic Advisory ("there is traffic near you") and then a coordinated Resolution Advisory ("you climb, the other descends"). We want the same for agents: a continuous notion of how close two agents are in code-space, so that as they approach we fire a trigger that makes them transmit what they're doing to each other and, if needed, makes one steer away — before they collide at the git/merge layer.
At time t, agent a has a working set
W_a = { (f, R_f, w_f) } (1)
where f is a touched file, R_f the set of edited line ranges in f, and w_f ∈ (0,1] a recency weight (older edits decay toward a floor). An agent may also declare an intent set I_a of files it is about to touch (look-ahead).
Two agents can collide through several independent channels. Each channel i yields a collision probability r_i ∈ [0,1]; we combine them as the probability of colliding through at least one channel:
R(a,b) = 1 − Π_i ( 1 − ω_i · r_i ) (2)
with channel weights ω_i ∈ [0,1]. The reported distance is the dual D(a,b) = 1 − R(a,b).
For shared files S = files(W_a) ∩ files(W_b):
lineOverlap(f) = |R_f^a ∩ R_f^b| / min(|R_f^a|, |R_f^b|) (overlap coefficient)
r_overlap = max_{f∈S} w_f^a·w_f^b · lineOverlap(f) (3)
The overlap coefficient (not Jaccard) is the right measure: it stays high when one
agent's small edit sits inside the other's large region (Jaccard would dilute it by
union size). A whole-file edit (no line info) ⇒ lineOverlap = 1. Same file,
overlapping lines ⇒ imminent collision; same file, disjoint line ranges (different
functions) ⇒ low r_overlap. Different files ⇒ no shared f ⇒ r_overlap = 0.
Build a dependency graph G=(V,E), edge f→g iff f imports g. Even when two files sit in distant subtrees, if one agent edits a file the other imports, the edit breaks the importer. Coupling decays with (direction-agnostic) graph distance d_G:
coupling(f,g) = γ^{ d_G(f,g) − 1 } γ ∈ (0,1), 0 if unreachable (4)
r_dep = max_{f∈W_a, g∈W_b} w_f · w_g · coupling(f,g) (5)
A direct import (d_G = 1) ⇒ coupling = 1. This is the "collision even when far away" term the metric must capture — a cross-file parameter/return dependency that fails at a distance.
For two paths with lowest-common-ancestor depth L:
treeDistance(f,g) = ((depth_f − L) + (depth_g − L)) / (depth_f + depth_g) (6)
r_tree = 1 − min_{f∈W_a, g∈W_b} treeDistance(f,g)
(0 = same file, 1 = disjoint roots.) Tree proximity alone rarely causes a collision, so ω_tree is small — it nudges the metric, never dominates it.
Call-graph distance (two functions near in the call stack), symbol-level read/write hazard (a writes a symbol b reads), and test-coverage overlap all slot in as additional r_i with their own weights — the noisy-OR (2) absorbs them without changing the framework.
Two thresholds carve a protected zone around R:
| Risk band | Advisory | Action |
|---|---|---|
R < τ_TA | Clear | nothing |
τ_TA ≤ R < τ_RA | Traffic Advisory | both agents transmit intent to each other (the scout handshake — "here is what I'm doing / did") |
R ≥ τ_RA | Resolution Advisory | the lower-priority agent steers away; the other holds course |
The resolution is coordinated and deterministic (like one plane climbing while the other descends) so the two agents never pick the same maneuver. Right-of-way priority:
priority(a) = ( committed-work(a), age(a) ) lexicographic
More committed work wins; ties break on earlier start; the final tiebreak is a stable agent id. The lower-priority agent receives the steer.
Closure rate. TCAS escalates on closing speed, not just separation. From two
risk samples Δt apart, closureRate = (R_t − R_{t−Δt}) / Δt; a positive closure
rate near τ_TA can pre-emptively escalate before the protected zone is entered.
Each file gets a coordinate via a space-filling embedding of its path (files
sharing a long directory prefix share most of their coordinate), then pulled
toward its dependency neighbours by one averaging step. An agent sits at the
recency-weighted centroid of its files' coordinates. The result: ‖v_a − v_b‖
tracks the collision risk R, so a 3D "where are the agents" view renders
agents as moving points in a file-cloud — you literally watch them crawl toward
each other, see the advisory line light up, and watch one steer away.
scanAirspace(agents, graph) returns, in one pass: the non-clear advisories
(what the trigger layer acts on), the 3D positions and fileCoordinates (what
the renderer draws), and pairwise links with risk (the edges to color).
buildDependencyGraph).scanAirspace; a Traffic Advisory
injects a "transmit intent" message between the two agents' sessions; a
Resolution Advisory tells the lower-priority agent to steer (re-target to a
different file/subtree) — the first concrete realization of just-in-time
multi-agent (and multi-human) deconfliction.