Summary
Second-pass well-written-code audit finding. AgentRegistry.AgentEntry protects writes to Versions with _gate, but exposes the mutable Dictionary publicly and ToInventory() enumerates it without taking that same lock.
Evidence
src/Arcp.Runtime/Agents/AgentRegistry.cs:65 builds inventory from _byName.Values.
src/Arcp.Runtime/Agents/AgentRegistry.cs:69 reads e.Versions.Count and enumerates e.Versions.Keys.ToArray() without locking AgentEntry._gate.
src/Arcp.Runtime/Agents/AgentRegistry.cs:79 exposes public Dictionary<string, IAgent> Versions.
src/Arcp.Runtime/Agents/AgentRegistry.cs:92 through src/Arcp.Runtime/Agents/AgentRegistry.cs:98 mutates that dictionary under _gate in AddVersion.
Why it matters
Concurrent registration and handshakes/inventory generation can enumerate a Dictionary while it is being mutated. That can throw InvalidOperationException, produce inconsistent welcome inventory, and makes the locking discipline hard to reason about.
Proposed fix
Hide Versions behind snapshot methods on AgentEntry, e.g. SnapshotInventoryFields() and TryGetVersion(), and ensure all dictionary reads/writes happen under _gate. Alternatively replace the mutable dictionary with immutable snapshots swapped atomically.
Acceptance criteria
- No public/internal API exposes the mutable
Dictionary instance.
ToInventory() obtains a consistent per-agent snapshot without enumerating unlocked mutable state.
- A concurrent test repeatedly calls
RegisterVersion and ToInventory without exceptions or partial state.
Summary
Second-pass well-written-code audit finding.
AgentRegistry.AgentEntryprotects writes toVersionswith_gate, but exposes the mutableDictionarypublicly andToInventory()enumerates it without taking that same lock.Evidence
src/Arcp.Runtime/Agents/AgentRegistry.cs:65builds inventory from_byName.Values.src/Arcp.Runtime/Agents/AgentRegistry.cs:69readse.Versions.Countand enumeratese.Versions.Keys.ToArray()without lockingAgentEntry._gate.src/Arcp.Runtime/Agents/AgentRegistry.cs:79exposespublic Dictionary<string, IAgent> Versions.src/Arcp.Runtime/Agents/AgentRegistry.cs:92throughsrc/Arcp.Runtime/Agents/AgentRegistry.cs:98mutates that dictionary under_gateinAddVersion.Why it matters
Concurrent registration and handshakes/inventory generation can enumerate a
Dictionarywhile it is being mutated. That can throwInvalidOperationException, produce inconsistent welcome inventory, and makes the locking discipline hard to reason about.Proposed fix
Hide
Versionsbehind snapshot methods onAgentEntry, e.g.SnapshotInventoryFields()andTryGetVersion(), and ensure all dictionary reads/writes happen under_gate. Alternatively replace the mutable dictionary with immutable snapshots swapped atomically.Acceptance criteria
Dictionaryinstance.ToInventory()obtains a consistent per-agent snapshot without enumerating unlocked mutable state.RegisterVersionandToInventorywithout exceptions or partial state.