Skip to content

Comments

feat(crewai): Add GenAI memory operation tracing for unified memory system#3713

Open
nagkumar91 wants to merge 1 commit intotraceloop:mainfrom
nagkumar91:nag/crewai-memory-ops
Open

feat(crewai): Add GenAI memory operation tracing for unified memory system#3713
nagkumar91 wants to merge 1 commit intotraceloop:mainfrom
nagkumar91:nag/crewai-memory-ops

Conversation

@nagkumar91
Copy link

@nagkumar91 nagkumar91 commented Feb 23, 2026

Summary

Adds memory operation tracing to the CrewAI instrumentor, aligned with the GenAI memory semantic conventions proposed in open-telemetry/semantic-conventions#3250.

The existing instrumentor traces Crew.kickoff, Agent.execute_task, Task.execute_sync, and LLM.call — but does not trace memory operations. CrewAI's new unified memory system (crewai.memory.unified_memory.Memory) has first-class remember(), recall(), forget(), and reset() methods that are critical for observability.

What changed

New wrappers in instrumentation.py:

CrewAI Method Span Operation Attributes
Memory.remember() update_memory scope, type, namespace, importance, update_strategy, memory ID
Memory.recall() search_memory scope, type, query (opt-in), result count
Memory.forget() delete_memory scope, record ID (single), deleted count
Memory.reset() delete_memory scope, reset indicator

All wrappers emit:

  • gen_ai.operation.name — memory operation type
  • gen_ai.system / gen_ai.provider.namecrewai
  • gen_ai.memory.scope — inferred from MemoryScope._root path (user/agent/session/team/global)
  • gen_ai.memory.type — inferred from categories (short_term/long_term/entity)
  • gen_ai.client.operation.duration — histogram metric
  • error.type — on failures
  • Content/query capture gated behind OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT

Graceful degradation:

Memory wrapping is in a try/except — older CrewAI versions without unified_memory won't break.

How has this been tested?

11 new tests in tests/test_memory_instrumentation.py:

  • TestMemoryRemember: 5 tests (basic, scope inference, content capture, no-content default, error)
  • TestMemoryRecall: 3 tests (basic + result count, query capture, error)
  • TestMemoryForget: 2 tests (scope-based + single record)
  • TestMemoryReset: 1 test (scope-level delete)

Related PRs


Important

Adds memory operation tracing for CrewAI's unified memory system, with tests for span attributes and error handling.

  • Behavior:
    • Adds tracing for Memory.remember(), Memory.recall(), Memory.forget(), and Memory.reset() in instrumentation.py.
    • Wrappers emit attributes like gen_ai.operation.name, gen_ai.system, gen_ai.memory.scope, and error.type.
    • Content capture is controlled by OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT.
    • Graceful degradation for older CrewAI versions without unified_memory.
  • Testing:
    • Adds test_memory_instrumentation.py with 11 tests for memory operations.
    • Tests cover basic functionality, scope inference, content capture, and error handling for each memory operation.

This description was created by Ellipsis for ae95a6b. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added instrumentation for CrewAI memory operations (remember, recall, forget, reset) with OpenTelemetry span tracking and semantic attributes.
    • Added optional message content capture controlled by environment variable configuration.
    • Enhanced telemetry for memory operations including error tracking and duration metrics.
  • Tests

    • Added comprehensive test coverage for memory operation instrumentation including edge cases and error scenarios.

…set)

Add GenAI memory semantic convention spans for CrewAI's unified memory system:

- Memory.remember() → update_memory span
  - Captures importance, scope, namespace, update_strategy (merge)
  - Records memory ID from returned MemoryRecord

- Memory.recall() → search_memory span
  - Captures query (opt-in), scope, result count
  - Infers memory type from categories

- Memory.forget() → delete_memory span
  - Captures scope, individual record ID when deleting single records
  - Reports deleted_count from return value

- Memory.reset() → delete_memory span
  - Scope-level deletion with reset indicator

All wrappers:
- Set gen_ai.operation.name, gen_ai.system, gen_ai.provider.name
- Infer gen_ai.memory.scope from MemoryScope._root path
- Record gen_ai.client.operation.duration metric
- Set error.type on failures
- Gate content/query capture behind OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT

Aligned with GenAI memory semantic conventions:
open-telemetry/semantic-conventions#3250

11 new tests covering all 4 operations + content capture + error handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

The pull request introduces GenAI memory instrumentation for the CrewAI instrumentor, adding wrappers for memory operations (remember, recall, forget, reset) that create OpenTelemetry spans with semantic attributes. It includes environment-controlled content capture, per-operation instruments, error handling, and comprehensive test coverage for the new functionality.

Changes

Cohort / File(s) Summary
Memory Instrumentation Implementation
packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py
Adds memory operation wrappers (remember, recall, forget, reset) with span creation, semantic attribute population (operation, provider, scope, type, content, memory ids), conditional content capture via environment variable, error recording, and unwrapping logic.
Memory Instrumentation Tests
packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py
Adds comprehensive test suite validating memory instrumentation across operation flows, context inference, content capture conditions, error propagation, and span attribute correctness using span exporters and fixtures.

Sequence Diagram(s)

sequenceDiagram
    participant App as CrewAI App
    participant Wrapper as Memory Wrapper
    participant Tracer as Tracer/Span
    participant Mem as Memory Engine
    
    App->>Wrapper: Call memory operation<br/>(remember/recall/forget/reset)
    Wrapper->>Tracer: Start span
    Wrapper->>Tracer: Set operation attribute
    Wrapper->>Tracer: Set scope/type/provider attributes
    rect rgba(100, 150, 200, 0.5)
        Wrapper->>Tracer: Capture content<br/>(if OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT)
    end
    Wrapper->>Mem: Execute operation
    alt Operation Success
        Mem-->>Wrapper: Return result<br/>(memory ids, counts, etc.)
        Wrapper->>Tracer: Set result attributes
    else Operation Error
        Mem-->>Wrapper: Raise exception
        Wrapper->>Tracer: Record error status
        Wrapper->>Tracer: Record error type attribute
    end
    Wrapper->>Tracer: End span
    Wrapper-->>App: Return result or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A hop and a span for memories kept,
With scopes and types in order swept,
Remember, recall, forget with care—
Telemetry traces weave through the air! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding GenAI memory operation tracing for the CrewAI unified memory system, which matches the core functionality introduced in the instrumentation.py file.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to ae95a6b in 14 seconds. Click for details.
  • Reviewed 554 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_xXZ8pjZxEtTZtE4w

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py (3)

351-352: Define a constant for "gen_ai.memory.id" instead of repeating raw string literals.

"gen_ai.memory.id" is used as a raw string at line 352 and again at line 431 (in wrap_memory_forget). This is inconsistent with the constants block at the top of the file and violates the guideline to leverage the semconv package (or explicit constants) for all attribute keys.

♻️ Proposed fix

Add to the constants block (lines 21–37):

+_GEN_AI_MEMORY_ID = getattr(GenAIAttributes, "GEN_AI_MEMORY_ID", "gen_ai.memory.id")

Then replace both raw string usages:

-                    set_span_attribute(span, "gen_ai.memory.id", str(result.id))
+                    set_span_attribute(span, _GEN_AI_MEMORY_ID, str(result.id))
-                set_span_attribute(span, "gen_ai.memory.id", str(record_ids[0]))
+                set_span_attribute(span, _GEN_AI_MEMORY_ID, str(record_ids[0]))

Based on learnings: "Instrumentation packages should leverage the semantic conventions package to generate spans and tracing data compliant with OpenTelemetry semantic conventions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`
around lines 351 - 352, Add a named constant for the attribute key used for
memory IDs instead of the raw string literal; update the constants block at the
top of the file (alongside existing constants) with something like
GEN_AI_MEMORY_ID = "gen_ai.memory.id" and then replace the raw string usages in
set_span_attribute(span, "gen_ai.memory.id", ...) inside the function where
result.id is set and in wrap_memory_forget where "gen_ai.memory.id" is
referenced to use the new GEN_AI_MEMORY_ID constant; ensure imports/namespace
match existing style and run tests to confirm no regressions.

74-89: Narrow the exception guard to avoid swallowing programming errors.

The intent is to tolerate older CrewAI versions that don't have unified_memory. But except Exception: pass will also silently absorb bugs like incorrect argument counts to wrap_function_wrapper, name typos, or any exception raised during wrapping — making them very hard to diagnose. Only ImportError and AttributeError need to be tolerated here.

🛠 Proposed fix
-        except Exception:
-            # CrewAI versions before unified_memory may not have these classes
-            pass
+        except (ImportError, AttributeError):
+            # Older CrewAI versions may not have unified_memory
+            pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`
around lines 74 - 89, The broad except Exception is hiding real bugs during
instrumentation; change the catch around the wrap_function_wrapper calls
(wrapping "crewai.memory.unified_memory" methods Memory.remember, Memory.recall,
Memory.forget, Memory.reset) to only tolerate ImportError and AttributeError
(e.g., use except (ImportError, AttributeError): pass) so missing unified_memory
in older CrewAI is allowed but other errors during wrap_function_wrapper remain
visible.

310-480: Consider extracting the repeated span/timing/error boilerplate into a shared context manager.

Each of the four memory wrappers repeats ~20 lines of identical scaffolding (timing, span creation with GEN_AI_SYSTEM, setting _GEN_AI_OPERATION_NAME/_GEN_AI_PROVIDER_NAME, try/except/finally with _record_memory_duration). A small context manager would eliminate the duplication and make future changes (e.g., adding a new metric label) apply in one place.

♻️ Example refactor sketch
from contextlib import contextmanager

`@contextmanager`
def _memory_span(tracer, duration_histogram, operation):
    error_type = None
    start = time.time()
    with tracer.start_as_current_span(
        f"{operation} {_PROVIDER}", kind=SpanKind.CLIENT,
        attributes={GenAIAttributes.GEN_AI_SYSTEM: _PROVIDER},
    ) as span:
        set_span_attribute(span, _GEN_AI_OPERATION_NAME, operation)
        set_span_attribute(span, _GEN_AI_PROVIDER_NAME, _PROVIDER)
        try:
            yield span
            span.set_status(Status(StatusCode.OK))
        except Exception as ex:
            error_type = _set_memory_error(span, ex)
            raise
        finally:
            _record_memory_duration(duration_histogram, time.time() - start, operation, error_type)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`
around lines 310 - 480, The four wrappers (wrap_memory_remember,
wrap_memory_recall, wrap_memory_forget, wrap_memory_reset) duplicate span
creation, timing, status/error handling and final _record_memory_duration;
extract that logic into a context manager (e.g. _memory_span(tracer,
duration_histogram, operation)) that starts the span with
GenAIAttributes.GEN_AI_SYSTEM and sets _GEN_AI_OPERATION_NAME and
_GEN_AI_PROVIDER_NAME, yields the span for function-specific attributes, sets
StatusCode.OK on success, calls _set_memory_error(span, ex) in the except block,
and always calls _record_memory_duration(duration_histogram, elapsed, operation,
error_type) in finally; then refactor each wrapper to call tracer/_memory_span
and only set the per-operation attributes (scope, type, content, ids, etc.) and
invoke wrapped(*args, **kwargs) inside the context manager.
packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py (2)

179-189: test_recall_error is missing the span status assertion present in test_remember_error.

test_remember_error (line 145) checks both span.status.status_code == StatusCode.ERROR and attrs[_ERROR_TYPE]. test_recall_error only checks _ERROR_TYPE, leaving the status-code path untested for the recall wrapper.

💚 Proposed fix
         span, attrs = _get_span(exporter)
+        assert span.status.status_code == StatusCode.ERROR
         assert attrs[_ERROR_TYPE] == "TimeoutError"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py`
around lines 179 - 189, The test_recall_error currently asserts only the error
type attribute but omits the span status assertion; update test_recall_error
(which invokes wrap_memory_recall) to mirror test_remember_error by fetching
span via _get_span(exporter) and asserting span.status.status_code ==
StatusCode.ERROR in addition to the existing assert attrs[_ERROR_TYPE] ==
"TimeoutError" so the recall wrapper's error status path is covered.

37-49: Replace the hand-rolled _InMemoryExporter with the SDK-provided InMemorySpanExporter.

opentelemetry.sdk.trace.export.in_memory_span_exporter.InMemorySpanExporter already provides this exact functionality (including get_finished_spans()). The custom class adds maintenance surface with no benefit.

♻️ Proposed fix
-from opentelemetry.sdk.trace.export import (
-    SimpleSpanProcessor,
-    SpanExporter,
-    SpanExportResult,
-)
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
-class _InMemoryExporter(SpanExporter):
-    def __init__(self):
-        self.spans = []
-
-    def export(self, spans):
-        self.spans.extend(spans)
-        return SpanExportResult.SUCCESS
-
-    def shutdown(self):
-        pass
-
-    def get_finished_spans(self):
-        return list(self.spans)

Update the fixture:

 `@pytest.fixture`()
 def exporter(tracer_provider):
-    exp = _InMemoryExporter()
+    exp = InMemorySpanExporter()
     tracer_provider.add_span_processor(SimpleSpanProcessor(exp))
     return exp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py`
around lines 37 - 49, Replace the custom _InMemoryExporter class in tests (class
_InMemoryExporter, its methods export/shutdown/get_finished_spans) with the
SDK-provided InMemorySpanExporter: import InMemorySpanExporter from
opentelemetry.sdk.trace.export.in_memory_span_exporter and use that exporter in
the test fixtures where _InMemoryExporter is constructed so you no longer define
export/shutdown/get_finished_spans yourself; keep usage of get_finished_spans()
as-is since the SDK class provides it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`:
- Line 325: The code currently hardcodes the memory update strategy by calling
set_span_attribute(span, _GEN_AI_MEMORY_UPDATE_STRATEGY, "merge"); instead,
derive the actual strategy from the runtime context (e.g., inspect the
Memory.remember() call arguments or the Memory instance—check
kwargs.get("strategy") or getattr(memory_instance, "strategy", None)) and only
call set_span_attribute when a concrete strategy value is present; if you cannot
determine it, omit setting the _GEN_AI_MEMORY_UPDATE_STRATEGY attribute to avoid
reporting incorrect telemetry.
- Around line 342-345: The content-capture guard currently skips keyword-only
calls and contains dead code; change the if to check content capture and
presence of content in either args or kwargs (e.g., if _capture_content() and
(args or "content" in kwargs):), then set content = args[0] if args else
kwargs.get("content"), and finally if content and isinstance(content, str): call
set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content); this removes the
unreachable branches and ensures keyword argument captures work for the content
capture logic.
- Around line 263-273: The _infer_memory_scope function currently reads a
non-existent instance._root and should be replaced to extract the scope from the
kwargs passed into Memory methods: change _infer_memory_scope to accept kwargs,
read scope = kwargs.get("scope"), validate it's a string, split/strip it and
return the first component if it's one of
("user","agent","session","team","global"), otherwise return "agent"; then
update all call sites to pass kwargs (i.e. call _infer_memory_scope(kwargs))
inside wrap_memory_remember, wrap_memory_recall, wrap_memory_forget, and
wrap_memory_reset so telemetry uses the actual scope parameter.

---

Nitpick comments:
In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`:
- Around line 351-352: Add a named constant for the attribute key used for
memory IDs instead of the raw string literal; update the constants block at the
top of the file (alongside existing constants) with something like
GEN_AI_MEMORY_ID = "gen_ai.memory.id" and then replace the raw string usages in
set_span_attribute(span, "gen_ai.memory.id", ...) inside the function where
result.id is set and in wrap_memory_forget where "gen_ai.memory.id" is
referenced to use the new GEN_AI_MEMORY_ID constant; ensure imports/namespace
match existing style and run tests to confirm no regressions.
- Around line 74-89: The broad except Exception is hiding real bugs during
instrumentation; change the catch around the wrap_function_wrapper calls
(wrapping "crewai.memory.unified_memory" methods Memory.remember, Memory.recall,
Memory.forget, Memory.reset) to only tolerate ImportError and AttributeError
(e.g., use except (ImportError, AttributeError): pass) so missing unified_memory
in older CrewAI is allowed but other errors during wrap_function_wrapper remain
visible.
- Around line 310-480: The four wrappers (wrap_memory_remember,
wrap_memory_recall, wrap_memory_forget, wrap_memory_reset) duplicate span
creation, timing, status/error handling and final _record_memory_duration;
extract that logic into a context manager (e.g. _memory_span(tracer,
duration_histogram, operation)) that starts the span with
GenAIAttributes.GEN_AI_SYSTEM and sets _GEN_AI_OPERATION_NAME and
_GEN_AI_PROVIDER_NAME, yields the span for function-specific attributes, sets
StatusCode.OK on success, calls _set_memory_error(span, ex) in the except block,
and always calls _record_memory_duration(duration_histogram, elapsed, operation,
error_type) in finally; then refactor each wrapper to call tracer/_memory_span
and only set the per-operation attributes (scope, type, content, ids, etc.) and
invoke wrapped(*args, **kwargs) inside the context manager.

In
`@packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py`:
- Around line 179-189: The test_recall_error currently asserts only the error
type attribute but omits the span status assertion; update test_recall_error
(which invokes wrap_memory_recall) to mirror test_remember_error by fetching
span via _get_span(exporter) and asserting span.status.status_code ==
StatusCode.ERROR in addition to the existing assert attrs[_ERROR_TYPE] ==
"TimeoutError" so the recall wrapper's error status path is covered.
- Around line 37-49: Replace the custom _InMemoryExporter class in tests (class
_InMemoryExporter, its methods export/shutdown/get_finished_spans) with the
SDK-provided InMemorySpanExporter: import InMemorySpanExporter from
opentelemetry.sdk.trace.export.in_memory_span_exporter and use that exporter in
the test fixtures where _InMemoryExporter is constructed so you no longer define
export/shutdown/get_finished_spans yourself; keep usage of get_finished_spans()
as-is since the SDK class provides it.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c2974c9 and ae95a6b.

📒 Files selected for processing (2)
  • packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py
  • packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py

Comment on lines +263 to +273
def _infer_memory_scope(instance) -> str:
"""Infer memory scope from the Memory instance or its MemoryScope wrapper."""
# MemoryScope has a _root attribute like "/agent/1" or "/user/123"
root = getattr(instance, "_root", None)
if root:
parts = root.strip("/").split("/")
if parts:
first = parts[0].lower()
if first in ("user", "agent", "session", "team", "global"):
return first
return "agent"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the instrumentation file mentioned in the review
find . -name "instrumentation.py" -path "*crewai*" | head -5

Repository: traceloop/openllmetry

Length of output: 168


🏁 Script executed:

# Search for Memory and MemoryScope references in the codebase
rg "class Memory|class MemoryScope" -A 5

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

# Look for unified_memory references
rg "unified_memory\|MemoryScope\|_root" --type py

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

# Read the instrumentation.py file to see the complete context and usage
cat -n ./packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py | head -300

Repository: traceloop/openllmetry

Length of output: 14435


🏁 Script executed:

# Search for calls to _infer_memory_scope
rg "_infer_memory_scope" --type py -B 3 -A 3

Repository: traceloop/openllmetry

Length of output: 5424


🏁 Script executed:

# Check dependencies for CrewAI version
find . -name "pyproject.toml" -o -name "requirements.txt" | xargs grep -l "crewai"

Repository: traceloop/openllmetry

Length of output: 204


🏁 Script executed:

# Check the crewai dependency version in the CrewAI instrumentation package
cat ./packages/opentelemetry-instrumentation-crewai/pyproject.toml | grep -A 20 "dependencies"

Repository: traceloop/openllmetry

Length of output: 926


🏁 Script executed:

# Now let's search for the Memory class usage and see what object is actually passed to _infer_memory_scope
# Look at the memory wrapper functions
sed -n '300,450p' ./packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py

Repository: traceloop/openllmetry

Length of output: 6426


🏁 Script executed:

# Look for tests of the memory instrumentation to see how Memory objects are used
find . -name "*test*" -type f -path "*crewai*" | head -10

Repository: traceloop/openllmetry

Length of output: 234


🏁 Script executed:

# Look for any mocks or references to Memory structure in tests
rg "Memory\|_root\|MemoryScope" --type py -A 2 -B 2 ./packages/opentelemetry-instrumentation-crewai/tests/ 2>/dev/null || echo "No tests directory found"

Repository: traceloop/openllmetry

Length of output: 89


🏁 Script executed:

# Check if there's documentation or comments about Memory structure elsewhere
rg "unified_memory\|MemoryScope" --type py ./packages/opentelemetry-instrumentation-crewai/

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

# Check the memory instrumentation tests to see how Memory objects are used
cat ./packages/opentelemetry-instrumentation-crewai/tests/test_memory_instrumentation.py

Repository: traceloop/openllmetry

Length of output: 7888


🏁 Script executed:

# Let's check if there's any documentation or comments about Memory's _root attribute
# in the instrumentation code
rg "Memory\|_root" --type py -B 3 -A 3 ./packages/opentelemetry-instrumentation-crewai/

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

# Let's also check the git history to see if there are comments about this
git log --oneline -n 20 -- ./packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py 2>/dev/null || echo "Git history not available"

Repository: traceloop/openllmetry

Length of output: 147


🌐 Web query:

CrewAI Memory class _root attribute MemoryScope unified_memory

💡 Result:

In CrewAI’s unified memory system, MemoryScope is essentially a scoped “view” over a single underlying Memory instance: it limits operations (remember/recall/forget, etc.) to one branch of the scope tree (e.g. /agent/researcher). The private MemoryScope._root attribute is the internal reference to that underlying root Memory object that actually performs the storage/retrieval; you typically shouldn’t access _root directly—use the public API like memory.scope("/path"). [1]

unified_memory is also used by CrewAI as the source_type emitted in memory-related events for this unified system. [1]

If you’re seeing inconsistent behavior/imports (e.g., from crewai import Memory not working), it may be because your installed crewai package version differs from what the docs/repo show; CrewAI community reports mismatches between “separate memory classes” vs the newer unified-memory exports (crewai.memory.unified_memory.Memory, MemoryScope, etc.). [2]

Sources: [1] CrewAI Memory docs (unified memory, MemoryScope, unified_memory events) https://docs.crewai.com/ko/concepts/memory, [2] CrewAI community thread showing unified-memory module exports vs older imports https://community.crewai.com/t/was-getting-error-while-importing-memory-using-the-same-method-displayed-in-docs/7344


🌐 Web query:

crewai 0.80.0 Memory class unified_memory source code

💡 Result:

In crewai==0.80.0 there is no unified_memory module / Memory class under crewai.memory.unified_memory (those paths 404 at the 0.80.0 tag). [2]

The Memory class that exists in 0.80.0 is the base class at:

  • src/crewai/memory/memory.py [1]

Source code (as of tag 0.80.0):

from typing import Any, Dict, Optional, List
from crewai.memory.storage.rag_storage import RAGStorage

class Memory:
    """ Base class for memory, now supporting agent tags and generic metadata.
    """
    def __init__(self, storage: RAGStorage):
        self.storage = storage

    def save(
        self,
        value: Any,
        metadata: Optional[Dict[str, Any]] = None,
        agent: Optional[str] = None,
    ) -> None:
        metadata = metadata or {}
        if agent:
            metadata["agent"] = agent
        self.storage.save(value, metadata)

    def search(self, query: str) -> List[Dict[str, Any]]:
        return self.storage.search(query)
``` [1]

**Sources:** [1] [2]

---

🌐 Web query:

`CrewAI Memory class attributes scope root unified_memory`

💡 Result:

In CrewAIs **unified `Memory`** system:

- **`scope`**: a hierarchicalfilesystem-likepath that partitions memories (e.g. `/`, `/project/alpha`, `/agent/researcher`). Recalls can be limited to a scope/branch, and `memory.scope("/agent/researcher")` returns a scoped view (a `MemoryScope`) that only reads/writes within that subtree. [1]  
- **`root`**: the **root scope is `/`** (the top of the scope tree). [1]  
- **`unified_memory`**: memory operations emit events tagged with **`source_type="unified_memory"`** (useful for filtering memory-related events in listeners). [2]

**Sources:** CrewAI Memory docs (scopes/root + `MemoryScope`) [1], CrewAI Memory docs (events `source_type="unified_memory"`) [2].

---

🌐 Web query:

`crewai unified_memory Memory class version support when added`

💡 Result:

CrewAIs **unified memory `Memory` class** (i.e., `from crewai import Memory`, implemented in `crewai.memory.unified_memory`) was introduced by PR **New Unified Memory System`#4420`**, which was **merged on Feb 14, 2026**. [1]

The first CrewAI release that explicitly includes this change is the **pre-release `1.10.0a1` (Feb 19, 2026)**, whose release notes list **New Unified Memory System**. [2]

This unified system replaces the older separate memory types (Short/Long-term, Entity, External) with a single `Memory` API, as reflected in the current docs. [1][3]

**Sources**
[1] PR `#4420` (merged Feb 14, 2026) – crewAIInc/crewAI  
[2] GitHub Releases page (shows `1.10.0a1`, Feb 19, 2026, includesNew Unified Memory System”) – crewAIInc/crewAI  
[3] CrewAI docs: Memory concept page describing the unified `Memory` classdocs.crewai.com

---

🌐 Web query:

`site:github.com crewai memory.unified_memory Memory _root attribute`

💡 Result:

In the current CrewAI codebase, `crewai.memory.unified_memory.Memory` **does not have** a `_root` attribute—it’s a regular Python class (not a Pydantic RootModel). Its internal state is kept in attributes like `_config`, `_storage`, `_llm_instance`, etc. [1]

If youre trying to work with therootof the memory hierarchy, CrewAI models that as a **scope path string**, where the root scope is `"/"` (and APIs take `scope` / `scope_prefix`, e.g., storage `reset(scope_prefix=...)`). [2]

**Sources**: [1], [2]

---



</details>

**Replace `_infer_memory_scope` to extract scope from `kwargs` instead of relying on a non-existent `instance._root` attribute.**

The CrewAI `Memory` class (both in versions 0.80.0 and the current unified_memory system) does **not** have a `_root` attribute. The current implementation will always return `"agent"` as a fallback, silently producing incorrect scope values in telemetry for all non-agent memory operations.

The scope information is already available in the `kwargs` passed to `Memory.remember()`, `Memory.recall()`, `Memory.forget()`, and `Memory.reset()` as the `scope` parameter. Extract and parse the scope from `kwargs` instead:

```python
def _infer_memory_scope(kwargs) -> str:
    """Infer memory scope from the scope kwarg."""
    scope = kwargs.get("scope")
    if scope and isinstance(scope, str):
        parts = scope.strip("/").split("/")
        if parts:
            first = parts[0].lower()
            if first in ("user", "agent", "session", "team", "global"):
                return first
    return "agent"

Then update all call sites to pass kwargs instead of instance:

  • _infer_memory_scope(kwargs) in wrap_memory_remember
  • _infer_memory_scope(kwargs) in wrap_memory_recall
  • _infer_memory_scope(kwargs) in wrap_memory_forget
  • _infer_memory_scope(kwargs) in wrap_memory_reset
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`
around lines 263 - 273, The _infer_memory_scope function currently reads a
non-existent instance._root and should be replaced to extract the scope from the
kwargs passed into Memory methods: change _infer_memory_scope to accept kwargs,
read scope = kwargs.get("scope"), validate it's a string, split/strip it and
return the first component if it's one of
("user","agent","session","team","global"), otherwise return "agent"; then
update all call sites to pass kwargs (i.e. call _infer_memory_scope(kwargs))
inside wrap_memory_remember, wrap_memory_recall, wrap_memory_forget, and
wrap_memory_reset so telemetry uses the actual scope parameter.

set_span_attribute(span, _GEN_AI_PROVIDER_NAME, _PROVIDER)
set_span_attribute(span, _GEN_AI_MEMORY_SCOPE, _infer_memory_scope(instance))
set_span_attribute(span, _GEN_AI_MEMORY_TYPE, _infer_memory_type(kwargs))
set_span_attribute(span, _GEN_AI_MEMORY_UPDATE_STRATEGY, "merge")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded "merge" strategy may produce inaccurate telemetry.

_GEN_AI_MEMORY_UPDATE_STRATEGY is set to "merge" regardless of what CrewAI's Memory.remember() actually does. If CrewAI uses other strategies (e.g., "overwrite", "append") depending on instance configuration or call arguments, every span will report the wrong strategy. Either read this value from the instance/kwargs, or omit the attribute until the actual strategy is observable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`
at line 325, The code currently hardcodes the memory update strategy by calling
set_span_attribute(span, _GEN_AI_MEMORY_UPDATE_STRATEGY, "merge"); instead,
derive the actual strategy from the runtime context (e.g., inspect the
Memory.remember() call arguments or the Memory instance—check
kwargs.get("strategy") or getattr(memory_instance, "strategy", None)) and only
call set_span_attribute when a concrete strategy value is present; if you cannot
determine it, omit setting the _GEN_AI_MEMORY_UPDATE_STRATEGY attribute to avoid
reporting incorrect telemetry.

Comment on lines +342 to +345
if _capture_content() and args:
content = args[0] if args else kwargs.get("content")
if content and isinstance(content, str):
set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Content capture silently fails for keyword-only callers, and contains dead code.

Two issues:

  1. The guard if _capture_content() and args: skips capture entirely when content is passed as a keyword argument (e.g., remember(content="...")), even if content capture is enabled. The kwargs.get("content") fallback inside the block is unreachable because args is always truthy at that point.

  2. The inner ternary args[0] if args else kwargs.get("content") is dead code: the outer condition already guarantees args is truthy.

🐛 Proposed fix
-            # Content (opt-in)
-            if _capture_content() and args:
-                content = args[0] if args else kwargs.get("content")
-                if content and isinstance(content, str):
-                    set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content)
+            # Content (opt-in)
+            if _capture_content():
+                content = args[0] if args else kwargs.get("content")
+                if content and isinstance(content, str):
+                    set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _capture_content() and args:
content = args[0] if args else kwargs.get("content")
if content and isinstance(content, str):
set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content)
# Content (opt-in)
if _capture_content():
content = args[0] if args else kwargs.get("content")
if content and isinstance(content, str):
set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-crewai/opentelemetry/instrumentation/crewai/instrumentation.py`
around lines 342 - 345, The content-capture guard currently skips keyword-only
calls and contains dead code; change the if to check content capture and
presence of content in either args or kwargs (e.g., if _capture_content() and
(args or "content" in kwargs):), then set content = args[0] if args else
kwargs.get("content"), and finally if content and isinstance(content, str): call
set_span_attribute(span, _GEN_AI_MEMORY_CONTENT, content); this removes the
unreachable branches and ensures keyword argument captures work for the content
capture logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants