Class InMemoryPendingToolInvocationStore
- All Implemented Interfaces:
PendingToolInvocationStore
PendingToolInvocationStore (OSS default). Entries are keyed by run id and
invocation id so a lookup for one run can never return another run's pending invocation.-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionclaim(String runId, String toolInvocationId, PendingToolInvocation expectedPending) Atomically removes and returns a pending invocation, but only if the row currently stored forrunId/toolInvocationIdis still exactlyexpectedPending— the same instance (or an equal value) an earlierPendingToolInvocationStore.find(java.lang.String, java.lang.String)on this call's behalf observed.Finds a pending invocation within its owning run.Returns every pending invocation currently awaiting resolution forrunId.voidRemoves a pending invocation; a no-op if it is absent.voidsave(PendingToolInvocation pending) Persists a pending invocation.verifyStillCurrent(String runId, String toolInvocationId, PendingToolInvocation expectedPending) Atomically checks whether the row currently stored forrunId/toolInvocationIdis still exactlyexpectedPending— the same exact-row-identity guaranteePendingToolInvocationStore.claim(java.lang.String, java.lang.String, com.agentforge4j.core.spi.tool.PendingToolInvocation)provides, but without removing the row.
-
Constructor Details
-
InMemoryPendingToolInvocationStore
public InMemoryPendingToolInvocationStore()
-
-
Method Details
-
save
Description copied from interface:PendingToolInvocationStorePersists a pending invocation.- Specified by:
savein interfacePendingToolInvocationStore- Parameters:
pending- the pending invocation
-
find
Description copied from interface:PendingToolInvocationStoreFinds a pending invocation within its owning run.- Specified by:
findin interfacePendingToolInvocationStore- Parameters:
runId- owning run idtoolInvocationId- pending invocation id- Returns:
- the pending invocation, or
nullif it does not belong torunIdor is gone
-
findByRun
Description copied from interface:PendingToolInvocationStoreReturns every pending invocation currently awaiting resolution forrunId. The result is scoped to the run — invocations belonging to other runs are never included — and is empty when the run has none pending. Used to resolve the single current pending invocation when a scripted approve/deny/retry/continue does not name an id; callers fail closed when the count is not one.- Specified by:
findByRunin interfacePendingToolInvocationStore- Parameters:
runId- owning run id- Returns:
- the run's pending invocations, in no guaranteed order; never
null
-
remove
Description copied from interface:PendingToolInvocationStoreRemoves a pending invocation; a no-op if it is absent.- Specified by:
removein interfacePendingToolInvocationStore- Parameters:
runId- owning run idtoolInvocationId- pending invocation id
-
claim
public PendingToolInvocation claim(String runId, String toolInvocationId, PendingToolInvocation expectedPending) Description copied from interface:PendingToolInvocationStoreAtomically removes and returns a pending invocation, but only if the row currently stored forrunId/toolInvocationIdis still exactlyexpectedPending— the same instance (or an equal value) an earlierPendingToolInvocationStore.find(java.lang.String, java.lang.String)on this call's behalf observed. This closes two race windows at once: when two callers race to resume the same invocation, at most one observes a non-nullresult and every other concurrent caller observesnullas though the row were already gone; and when the row is replaced (for example by a fresh pending row persisted after a claimed resume itself failed) between one caller'sPendingToolInvocationStore.find(java.lang.String, java.lang.String)and its own claim attempt, that caller's claim fails rather than consuming the replacement under the stale row's authorization.This is the sole safe way to consume a pending invocation before invoking its provider: a separate
PendingToolInvocationStore.find(java.lang.String, java.lang.String)followed by a separatePendingToolInvocationStore.remove(java.lang.String, java.lang.String)leaves a window in which two concurrent resumes both observe the row and both invoke the provider, or in which a caller claims a row that is no longer the one it decided to act on. Implementations backed by a durable or distributed store must provide this atomically — for example a conditional delete tied to the expected row's identity/version (compare-and-delete), an equivalent compare-and-swap, or aSELECT ... FOR UPDATEfollowed by a delete guarded by the same condition within the same transaction — never a naive find-then-remove pair, which would silently reopen the race this method exists to close.- Specified by:
claimin interfacePendingToolInvocationStore- Parameters:
runId- owning run idtoolInvocationId- pending invocation idexpectedPending- the pending invocation the caller observed and is claiming; the claim only succeeds if the currently stored row still equals this value- Returns:
- the claimed pending invocation, or
nullif it does not belong torunId, is already gone, was already claimed by a concurrent caller, or no longer equalsexpectedPending(it was replaced)
-
verifyStillCurrent
public PendingToolInvocation verifyStillCurrent(String runId, String toolInvocationId, PendingToolInvocation expectedPending) Description copied from interface:PendingToolInvocationStoreAtomically checks whether the row currently stored forrunId/toolInvocationIdis still exactlyexpectedPending— the same exact-row-identity guaranteePendingToolInvocationStore.claim(java.lang.String, java.lang.String, com.agentforge4j.core.spi.tool.PendingToolInvocation)provides, but without removing the row. Used when a caller must act on a previously observed row's content without itself consuming it, so a legitimate later resolver (for example aReject, or the runtime's non-executingContinue) can still resolve the row afterward.This exists because a caller re-issuing a plain
PendingToolInvocationStore.find(java.lang.String, java.lang.String)and comparing the result against its own earlier observation in application code is not equivalent to this method: that secondfindis itself just another independent read, with no contract binding it to observe a result consistent with any other caller's concurrentPendingToolInvocationStore.claim(java.lang.String, java.lang.String, com.agentforge4j.core.spi.tool.PendingToolInvocation)/PendingToolInvocationStore.save(com.agentforge4j.core.spi.tool.PendingToolInvocation)/PendingToolInvocationStore.remove(java.lang.String, java.lang.String)on the same row — an implementation would be free to satisfy both methods' individual contracts while still letting the two calls interleave with an intervening mutation in a way that leaves the caller's decision based on data that was never simultaneously true. This method's contract fixes that: it is required to observe the exact same total order overclaim/save/remove/findfor a given key thatPendingToolInvocationStore.claim(java.lang.String, java.lang.String, com.agentforge4j.core.spi.tool.PendingToolInvocation)'s compare-and-delete already relies on, so atrue-equivalent result here is a genuine, race-free confirmation thatexpectedPendingwas still the current row at a single well-defined point after every concurrent mutation that had already completed. Implementations backed by a durable or distributed store must provide this with the same atomicity guarantee asPendingToolInvocationStore.claim(java.lang.String, java.lang.String, com.agentforge4j.core.spi.tool.PendingToolInvocation)(for example a conditional read tied to the expected row's identity/version, consistent with whatever mechanism backsclaim's compare-and-delete) — never a plain, independently-timedfindcall assembled in caller code.- Specified by:
verifyStillCurrentin interfacePendingToolInvocationStore- Parameters:
runId- owning run idtoolInvocationId- pending invocation idexpectedPending- the pending invocation the caller previously observed- Returns:
expectedPendingif the currently stored row for this key still equals it;nullif it does not belong torunId, is already gone, or no longer equalsexpectedPending(it was claimed or replaced by a concurrent caller)
-