An agent plugin caches task context before injecting it into a system-message transform. Multiple root sessions and child sessions share one process. How should the cache be keyed and lifecycle-managed so context never leaks across sessions, and how should memory be bounded while the transform still sees the right snapshot?
Resolution (reasoning only; no executed tests; no repository inspection):
Isolation
- Do not keep task context in a process-global singleton or module-level currentContext. Key each cache entry by the concrete sessionId active at write and at inject time. Treat root and child sessions as distinct keys unless inheritance is an explicit product rule.
- Carry sessionId through request scope such as AsyncLocalStorage into the system-message transform. Module-level current-session pointers race under concurrent sessions and are the usual leak vector.
- On inject, look up only by the active session key. Missing means empty or default. Never fall back to parent, sibling, last-writer, or another session.
- Default-isolate children. If inheritance is required, copy-on-create a snapshot into the child key; never share a mutable object with the parent.
- Delete on session dispose and abort. Do not rely on GC of closed closures alone. Prefer immutable publish or per-key replace so a published entry is not mutated in place.
Auth-aware hardening when credentials can rotate while the same root session stays alive: key as sessionId plus authGeneration resolved from the live authorization handle at write, read, and inject. Sample generation once at write start; commit only if sampled still equals live inside the revoke critical section or equivalent CAS. Never re-key a late write onto the post-bump generation. On revoke: bump, sweep indexed lineage including children, then publish the new credential only if live still equals the bumped value. Injected prompt context is never a network credential; outbound calls must present a capability bound to the current generation and fail closed on mismatch.
Memory bounds
- Cap entry count, bytes per entry, and total bytes; reject or truncate oversized writes.
- Prefer delete-on-session-dispose and delete-on-auth-bump; use LRU plus TTL only for orphans.
- Store slim structured summaries, not full transcripts.
- Do not recycle a sessionId while an orphaned composite entry for that id may remain, or use a process-wide epoch so restarted generation counters cannot collide.
Failure modes to avoid: global Map overwrite, last-writer-wins currentContext, parent-lookup fallback, unbounded growth when sessions never dispose, and session-only keys that survive auth rotate.
This is reasoned design analysis aligned with existing public skill guidance on session- and auth-bound task context caches. Support is independent design review, not executed tests.
Changed requirement (reasoning only; no executed tests; no repository inspection): authorization can be revoked or replaced while the plugin process and the same root session stay alive.
Cache binding
- Extend every entry from sessionId alone to the composite key sessionId plus authGeneration.
- Keep authGeneration as a monotonic opaque counter on the live authorization handle for that auth lineage, not only inside cached payloads.
- Carry only sessionId in request scope such as AsyncLocalStorage. Resolve authGeneration from the live handle at write, read, and inject time—never from a module-level current pointer and never from an ALS-cached generation alone.
- Look up by the full composite key. Missing or generation mismatch injects empty or default. Never fall back to a prior generation, parent, sibling, or last-writer.
Write commit races
- Sample generation once at the start of a write. Key and tag the entry only with that sampled generation.
- On commit, inside the same critical section used for revoke or an equivalent compare-and-set, require sampled equals live; abort on mismatch.
- Never re-resolve live into the composite key after a bump, or a late writer can store pre-bump payload under the new generation.
Invalidate on replace
- In one critical section: bump generation, delete or tombstone older lineage entries including indexed children, then publish the new credential only if live.authGeneration still equals the bumped value.
- Default-isolate children; if inheritance is required, copy-on-create into the child key. Children must read the live parent auth handle for generation checks. Maintain a parent-to-child or auth-lineage index so revoke sweeps every tied key.
- Serialize child snapshot creation and lineage-index registration with that same critical section, or re-validate live generation after insert and delete the child entry on mismatch.
Prevent stale context from authorizing network activity
- Treat injected context as prompt data only—never as a network credential. Do not cache bearer tokens in task context.
- Every outbound network or tool call must present a capability bound to the current generation; mismatch fails closed.
- Cancel work that will take another hop; do not assume already-gated single-shot I/O is recalled.
- Re-check live generation before applying response side effects because bytes already on the wire cannot be recalled.
Memory: prefer delete-on-bump; keep generation state O(1) per live session; avoid recycling sessionId while an orphaned composite entry may remain, or use a process-wide epoch so restarted counters cannot collide.
Limits: does not scrub prompt text already injected into an in-flight model turn before a bump; completed single-shot I/O under an old generation is not undone.
This aligns with existing public skill guidance on session- and auth-bound task context caches. Support is reasoned design analysis and independent design review, not executed tests.