Category: bug Severity: minor
Location: crates/arcp-runtime/src/runtime/server.rs:222, :958-971
Spec: n/a
What
Every tool.invoke carrying an idempotency_key inserts a permanent IdempotencyRecord into the runtime-wide idempotency_index DashMap. There is no removal, eviction, or TTL anywhere in the codebase (grep shows only insert/get). A long-running runtime accumulates one entry per distinct key forever — an unbounded memory leak. This compounds issue #72 (terminal jobs never swept), but is a separate map with no sweep path at all.
Evidence
idempotency_index: DashMap<IdempotencyScope, IdempotencyRecord>,
// ...
self.inner.idempotency_index.insert(
scope,
IdempotencyRecord { /* accepted, tool, arguments_canonical */ },
);
No corresponding .remove( exists for this map.
Proposed fix
Bound the index: evict records when their job reaches a terminal state past the resume window, or cap the map with an LRU / time-based sweep invoked alongside the (currently-unwired) sweep_terminals. Document the retention contract — idempotency replay only needs to hold for the resume window.
Acceptance criteria
Category: bug Severity: minor
Location:
crates/arcp-runtime/src/runtime/server.rs:222,:958-971Spec: n/a
What
Every
tool.invokecarrying anidempotency_keyinserts a permanentIdempotencyRecordinto the runtime-wideidempotency_indexDashMap. There is no removal, eviction, or TTL anywhere in the codebase (grepshows onlyinsert/get). A long-running runtime accumulates one entry per distinct key forever — an unbounded memory leak. This compounds issue #72 (terminal jobs never swept), but is a separate map with no sweep path at all.Evidence
No corresponding
.remove(exists for this map.Proposed fix
Bound the index: evict records when their job reaches a terminal state past the resume window, or cap the map with an LRU / time-based sweep invoked alongside the (currently-unwired)
sweep_terminals. Document the retention contract — idempotency replay only needs to hold for the resume window.Acceptance criteria