Summary
When @agent, @task, and @crew are used on a CrewBase class, memoized method results are stored in a module-level cache. Repeatedly creating new crew instances (for example, per request) leaves each instance's generated Agent, Task, and Crew objects strongly referenced in CacheHandler._cache, so process memory grows with request count.
Affected versions
- Confirmed in
1.15.2
- Re-checked in
1.15.20 (wheel/source) and still present
Evidence in current implementation
crewai.project.utils defines global cache = CacheHandler()
crewai.project.annotations wraps agent, task, crew decorator outputs with memoize(...)
CacheHandler uses an internal plain dict without TTL/size bounds and stores entries as strong references
Reproduction (minimal)
- Define a
CrewBase class with at least one @agent, @task, and @crew method.
- In a loop, create many new crew instances and touch cached methods.
- Inspect
cache._cache and object liveness after gc.collect().
Expected: old instance results can be reclaimed when user code drops references.
Observed: results remain in cache even after instance refs are removed.
Why this breaks long-running services
Some services build crews per request for isolation. Under steady request flow this cache behavior causes global accumulation of per-instance runtime objects (including nested clients and state), and RSS continues to rise across completed requests.
Suggested fix direction
- Make memoization cache scoped to the
CrewBase instance rather than module scope, or
- Add an explicit invalidation/lifecycle API for CrewBase-bound memoized values, and call it when crews are discarded, while preserving backward compatibility for existing singleton-style usage.
If this behavior is expected for static class-like usage, documenting it would still help; however this makes per-request crew construction unsafe for memory-bounded processes.
Summary
When
@agent,@task, and@creware used on aCrewBaseclass, memoized method results are stored in a module-level cache. Repeatedly creating new crew instances (for example, per request) leaves each instance's generatedAgent,Task, andCrewobjects strongly referenced inCacheHandler._cache, so process memory grows with request count.Affected versions
1.15.21.15.20(wheel/source) and still presentEvidence in current implementation
crewai.project.utilsdefines globalcache = CacheHandler()crewai.project.annotationswrapsagent,task,crewdecorator outputs withmemoize(...)CacheHandleruses an internal plain dict without TTL/size bounds and stores entries as strong referencesReproduction (minimal)
CrewBaseclass with at least one@agent,@task, and@crewmethod.cache._cacheand object liveness aftergc.collect().Expected: old instance results can be reclaimed when user code drops references.
Observed: results remain in cache even after instance refs are removed.
Why this breaks long-running services
Some services build crews per request for isolation. Under steady request flow this cache behavior causes global accumulation of per-instance runtime objects (including nested clients and state), and RSS continues to rise across completed requests.
Suggested fix direction
CrewBaseinstance rather than module scope, orIf this behavior is expected for static class-like usage, documenting it would still help; however this makes per-request crew construction unsafe for memory-bounded processes.