Loop Memo
Memoisation helper for "is this the first time we've done X in the current agentic loop?" — the canonical pattern for one-shot tool work that should be paid once per LLM tool-loop iteration regardless of how many times the LLM invokes the host tool.
Use cases:
A skill-activation tool returns its body on the first call but a stern "already activated this turn" message on repeat calls (avoiding gateway-confusion loops where the LLM keeps trying different arg shapes).
A
describe(namespace)tool prepends bulky code-writing guidance on the first describe per loop only — subsequent describes carry just the namespace body since the LLM already has the guidance in conversation history.Any other "if I've already kicked this off this turn, don't redo it" gating where the dedup is loop-scoped (NOT process-scoped — different loops in the same agent process get distinct ids).
Backed by a thread-safe bounded LRU set keyed on the loop id read from ToolCallContext.loopId. When maxTracked entries is exceeded the oldest is evicted — exact upper bound, no transient overshoot under concurrent writers.
Fallback when the context has no loop id (callers that didn't set one — bare tests, out-of-loop invocations): firstTimeIn always returns true, on the safe-but-occasionally-noisy assumption that emitting the one-shot work is the less-bad failure mode vs swallowing it forever.
Example:
private val memo = LoopMemo()
override fun call(input: String, context: ToolCallContext): Tool.Result =
if (memo.firstTimeIn(context)) Tool.Result.text(skillBody)
else Tool.Result.text("Already activated this turn — see prior result.")Functions
Returns true the first time context's ToolCallContext.loopId is seen by this memo, false on subsequent calls within the same loop. When the context has no loop id, returns true every call.