Verifying fine-grained optimistic concurrent programs remains an open problem. Modern program logics provide abstraction mechanisms and compositional reasoning principles to deal with the inherent complexity. However, their use is mostly confined to pencil-and-paper or mechanized proofs. We devise a new separation logic geared towards the lacking automation. While local reasoning is known to be crucial for automation, we are the first to show how to retain this locality for (i) reasoning about inductive properties without the need for ghost code, and (ii) reasoning about computation histories in hindsight. We implemented our new logic in a tool and used it to automatically verify challenging concurrent search structures that require inductive properties and hindsight reasoning, such as the Harris set. 1 struct N = { val key: K; var next: N } 2 3 val tail = new N { key = ∞; next = tail } 4 val head = new N { key = −∞; next = tail } 5 6 procedure traverse( : K, : N, ln: N, : N): (N * N * N) { 7 val tn = .next 8 val tmark = is_marked(tn) 9 if (tmark) return traverse( , , ln, tn) 10 else if ( .key < ) return traverse( , , tn, tn) 11 else return ( , ln, ) 12 } 13 procedure find( : K): N * N { 14 val hn = head.next 15 val , ln, := traverse( , head, hn, hn) 16 if ((ln == || CAS( .next, ln, )) 17 && !is_marked( .next)) return ( , ) 18 else return find( ) 19 } 20 21 procedure search( : K) : Bool { 22 val _, = find( ) 23 return .key == 24 } −∞ 3 4 6 7 1 5 9 ∞ head ln 1 * next ( ) = ln * mark(ln) .next := * next ( ) = and the right update chunk is described by 2 * next ( ) = * mark( ) * next ( ) = tn .next := tn * next ( ) = tn .1 is derived inductively during the traversal of the marked segment from to using the same process that we are about to describe for deriving . The future 2 can be easily proved in isolation. In particular the precondition mark( ) implies C( ) = . Hence, the keyset invariant C( ) ⊆ KS( )