First divergence of a shadow run with reordered tool calls
Your task
Before a new version of an agent is promoted, it runs in shadow mode on the same tickets as the current version, and the two tool-call traces are compared. Exact equality is too strict, because the new version is free to issue independent calls in a different order. Two calls are independent when they touch no common resource, and swapping two adjacent independent calls never changes the outcome. A trace is equivalent to another if it can be turned into it by a series of such swaps.
Write first_divergence(current, candidate). Each trace is a list of calls [name, resources]. name identifies the call (its tool and arguments): the same name always touches the same resources, and two calls with the same name are interchangeable. resources lists the distinct resources the call reads or writes, possibly none. Names and resources can be any strings, even "constructor" or "__proto__", and can contain any character. Each trace has at most 2,000 calls, and each call touches at most 4 resources.
Return:
-1ifcandidateis equivalent tocurrent;- otherwise the smallest
isuch thatcandidate[:i + 1]is not how any trace equivalent tocurrentbegins; - or, if every prefix of
candidateis how some equivalent trace begins butcandidatestops before making all the calls,len(candidate).
Examples, all with current = [["read a", ["a"]], ["read b", ["b"]], ["write a", ["a"]]]:
candidate = [["read b", ["b"]], ["read a", ["a"]], ["write a", ["a"]]]returns-1:read bandread atouch different resources.candidate = [["write a", ["a"]], ["read a", ["a"]], ["read b", ["b"]]]returns0:write acan never move ahead ofread a, since both toucha.candidate = [["read b", ["b"]], ["read a", ["a"]]]returns2.
Examples
first_divergence(current: list[list], candidate: list[list]) -> intfirst_divergence([["read a", ["a"]], ["read b", ["b"]], ["write a", ["a"]]], [["read b", ["b"]], ["read a", ["a"]], ["write a", ["a"]]])-1first_divergence([["read a", ["a"]], ["read b", ["b"]], ["write a", ["a"]]], [["write a", ["a"]], ["read a", ["a"]], ["read b", ["b"]]])0first_divergence([["read a", ["a"]], ["read b", ["b"]], ["write a", ["a"]]], [["read b", ["b"]], ["read a", ["a"]]])2