Skip to content

feat(otel): add OpenTelemetry instrumentation for core memory operations - #2207

Open
henrikrexed wants to merge 6 commits into
MemTensor:mainfrom
henrikrexed:feat/memory-semconv-otel
Open

feat(otel): add OpenTelemetry instrumentation for core memory operations#2207
henrikrexed wants to merge 6 commits into
MemTensor:mainfrom
henrikrexed:feat/memory-semconv-otel

Conversation

@henrikrexed

Copy link
Copy Markdown

Summary

Adds first-class OpenTelemetry instrumentation to MemOS conforming to memory-semconv v0.1.0, so MemOS emits uniform, benchmark-grade telemetry — spans, metrics, and logs — on the core memory operations. It is opt-in (a no-op unless OTEL_EXPORTER_OTLP_ENDPOINT is set) and every optional dependency is imported defensively, so default installs and local runs are unaffected.

What's instrumented

Traces — spans on memory.add / memory.search / memory.update / memory.delete:

  • memory.tier — L1/L2/L3 mapped to textual / activation / parametric (+ preference)
  • memory.operation, memory.user.id, memory.cube.id, memory.session.id, memory.item.count, memory.result.count, memory.query.text (search), memory.latency.ms

Metrics — memory.operations (counter), memory.operation.duration (ms histogram), memory.result.count (histogram), labeled by memory.operation + memory.tier.

Logs — structured, span-correlated records on each operation.

Propagation — W3C TraceContext installed globally; both the REST (FastAPI) and MCP entry-points bootstrap the SDK from env and extract inbound traceparent, so memory.* spans nest under a calling agent's trace end-to-end.

Changes

  • src/memos/telemetry.py — new instrumentation module (providers, semconv constants, span/metric/log helpers, configure_from_env bootstrap, W3C propagation).
  • src/memos/mem_os/core.py — instrument core add/search/update/delete.
  • src/memos/multi_mem_cube/single_cube.py — instrument the product-API add/search path.
  • src/memos/api/server_api.py — bootstrap OTel + FastAPIInstrumentor.
  • src/memos/api/mcp_serve.py — bootstrap OTel on the MCP server entry-point (otherwise MCP-served MemOS exports nothing).
  • pyproject.toml — new [otel] optional-dependency extra.
  • tests/ — telemetry + MCP-bootstrap regression tests (13 passing).

Validation

Validated end-to-end against a live OTel Collector → Tempo and Dynatrace: one connected trace CrewAI agent → LLM (Ollama) → MCP → memos (single trace_id), memory.tier=textual semconv spans, and memory_* metrics on service.name=memos. Both backends agree. Unit tests: pytest tests/test_telemetry.py tests/test_mcp_serve_telemetry.py → 13 passed.

Notes

  • Install with pip install "MemoryOS[otel]". Optional auto-instrumentation (opentelemetry-instrumentation-mcp) is imported defensively and disabled if absent.
  • Apache-2.0; no CLA/DCO required.

henrikrexed and others added 2 commits August 2, 2026 19:56
Instrument the core add / search / update / delete memory operations with
OpenTelemetry spans, metrics, and logs, following the memory-semconv
conventions. The L1/L2/L3 memory tiers are mapped onto the `memory.tier`
attribute.

- New `memos.telemetry` module: lightweight helpers that emit spans/metrics
  and degrade to no-ops when OpenTelemetry is not installed or configured, so
  there is zero runtime cost for users who do not opt in.
- The application remains responsible for configuring the OTel SDK/exporters;
  this change only instruments the library (correct library/app separation).
- Metrics cover data stored/indexed/utilized, per-tier utilization, query
  result/hit counts, and search latency. The search query is captured as a
  span attribute with cardinality/PII in mind.
- Adds optional `opentelemetry-*` dependencies under an extra in pyproject.
- Adds tests/test_telemetry.py (all green without an OTel backend present).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…port, product-API metrics

Extends the core memory-semconv instrumentation so the telemetry is actually
exported on every entry-point MemOS runs behind, and validated end-to-end.

- telemetry.py: env bootstrap (configure_from_env), W3C TraceContext propagation,
  metrics (memory.operations, memory.operation.duration, memory.result.count) and
  span-correlated logs, alongside the add/search/update/delete spans.
- api/server_api.py: bootstrap the OTel SDK from env + FastAPIInstrumentor so the
  REST entry-point emits server spans and propagates traceparent.
- api/mcp_serve.py: bootstrap OTel in MOSMCPServer.__init__ so the MCP entry-point
  exports the memory.* telemetry too (otherwise it runs against the no-op provider
  and emits nothing over MCP); best-effort server-side MCP context extraction.
- multi_mem_cube/single_cube.py: instrument the product-API add/search path.
- pyproject.toml: opentelemetry-instrumentation-fastapi in the [otel] extra.
- tests: telemetry + MCP-bootstrap regression guards (13 passing).

All signals validated against a live OTel collector → Tempo + Dynatrace:
memory.tier semconv spans + memory_* metrics on service.name=memos, connected
end-to-end under an agent trace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Copilot AI lite review requested due to automatic review settings August 4, 2026 10:45
@Memtensor-AI Memtensor-AI added area:core MOS 编排层 / 框架底座 / 跨模块问题 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 4, 2026
@Memtensor-AI
Memtensor-AI requested a review from WeiminLee August 4, 2026 10:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds an OpenTelemetry instrumentation module and wires it into MemOS core memory operations, plus an optional dependency extra and basic unit tests intended to validate span/metric emission.

Changes:

  • Introduces src/memos/telemetry.py with span/metric/log helpers and a configure() routine.
  • Wraps MOSCore.search() in a memory_span(...) and decorates add/update/delete with @instrument_op(...).
  • Adds an otel optional-dependency extra and a new tests/test_telemetry.py suite.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
tests/test_telemetry.py New unit tests validating span attributes and in-memory metrics for the telemetry helpers.
src/memos/telemetry.py New telemetry helper module defining semconv constants plus span/metric/log utilities and an SDK configure function.
src/memos/mem_os/core.py Instruments core memory operations (search/add/update/delete) via the new telemetry helpers.
pyproject.toml Adds an [otel] optional-dependency extra for OpenTelemetry packages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/memos/mem_os/core.py Outdated
Comment on lines +12 to +16
from memos.telemetry import (
MEMORY_CUBE_ID,
MEMORY_ITEM_COUNT,
MEMORY_RESULT_COUNT,
MEMORY_SESSION_ID,
Comment thread src/memos/telemetry.py
Comment on lines +80 to +90
def configure(
service_name: str = "memos",
service_version: str = "0.0.0",
otlp_endpoint: str = "http://localhost:4317",
export_interval_ms: int = 5000,
) -> None:
"""
Configure OTel providers and install them globally.

Call once at application startup (e.g., from MOSConfig or CLI entry-point).
Safe to call multiple times; subsequent calls are no-ops.
Comment thread src/memos/telemetry.py Outdated
Comment on lines +25 to +27
from opentelemetry import metrics, trace
from opentelemetry._logs import get_logger_provider, set_logger_provider
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
Comment thread tests/test_telemetry.py Outdated
Comment on lines +6 to +17
import importlib.util
import pathlib
import sys
import types
import pytest

from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry import trace, metrics
@Memtensor-AI

Memtensor-AI commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #2207
Task: 468bce880403ade4
Base: main
Head: feat/memory-semconv-otel

🔍 OpenCodeReview found 13 issue(s) in this PR.


1. pyproject.toml (L74-L81)

The otel extra group uses unbounded upper-version constraints (e.g. >=1.27.0) while all other optional groups in this project enforce an upper bound (e.g. neo4j (>=5.28.1,<6.0.0)). OpenTelemetry follows semantic versioning but has historically introduced breaking changes across minor versions (especially for the SDK and exporters). Without an upper cap, a future opentelemetry-sdk 2.0.0 could silently break the instrumentation layer at install time. Consider adding a <2.0.0 ceiling to all six packages to align with the project's pinning strategy.

💡 Suggested Change

Before:

otel = [
    "opentelemetry-api (>=1.27.0)",
    "opentelemetry-sdk (>=1.27.0)",
    "opentelemetry-exporter-otlp-proto-grpc (>=1.27.0)",
    "opentelemetry-exporter-otlp-proto-http (>=1.27.0)",
    "opentelemetry-semantic-conventions (>=0.48b0)",
    "opentelemetry-instrumentation-fastapi (>=0.48b0)",
]

After:

otel = [
    "opentelemetry-api (>=1.27.0,<2.0.0)",
    "opentelemetry-sdk (>=1.27.0,<2.0.0)",
    "opentelemetry-exporter-otlp-proto-grpc (>=1.27.0,<2.0.0)",
    "opentelemetry-exporter-otlp-proto-http (>=1.27.0,<2.0.0)",
    "opentelemetry-semantic-conventions (>=0.48b0,<1.0.0)",
    "opentelemetry-instrumentation-fastapi (>=0.48b0,<1.0.0)",
]

2. pyproject.toml (L75-L80)

The same six OTel packages are duplicated verbatim in the all group instead of referencing the otel extra (e.g. MemoryOS[otel]). This creates a maintenance burden: version bumps must be applied in two places and it is easy for the two copies to drift out of sync. The all group should reference the otel extra by name, just as tree-mem, mem-scheduler, etc. would be referenced.

💡 Suggested Change

Before:

    "opentelemetry-api (>=1.27.0)",
    "opentelemetry-sdk (>=1.27.0)",
    "opentelemetry-exporter-otlp-proto-grpc (>=1.27.0)",
    "opentelemetry-exporter-otlp-proto-http (>=1.27.0)",
    "opentelemetry-semantic-conventions (>=0.48b0)",
    "opentelemetry-instrumentation-fastapi (>=0.48b0)",

After:

    "MemoryOS[otel]",

3. pyproject.toml (L78-L79)

opentelemetry-exporter-otlp-proto-http is declared as a dependency but telemetry.py only imports from opentelemetry.exporter.otlp.proto.grpc.* (gRPC exporters). The HTTP exporter is never imported or used in the current codebase. Including an unused package increases install size and may cause confusion. Remove it unless HTTP export support is planned in the near term.


4. src/memos/api/mcp_serve.py (L51-L63)

The try/except ImportError block covers both the import statement and the MCPInstrumentor().instrument() call. If instrument() raises an ImportError due to a missing transitive dependency, it will be silently swallowed and incorrectly logged as "package not installed", masking a real instrumentation failure that is distinct from the package being absent.

Separate the import guard from the instrumentation call so each failure is handled and reported correctly.

💡 Suggested Change

Before:

        try:
            from opentelemetry.instrumentation.mcp import MCPInstrumentor

            MCPInstrumentor().instrument()
            logger.info(
                "[MCP_SERVE] MCP instrumentation active — trace context propagates over MCP"
            )
        except ImportError:
            logger.warning(
                "[MCP_SERVE] opentelemetry-instrumentation-mcp NOT installed: memos spans "
                "will export but start a NEW trace instead of nesting under the caller. "
                "Install it (client + server) for end-to-end cross-service traces."
            )

After:

        try:
            from opentelemetry.instrumentation.mcp import MCPInstrumentor
        except ImportError:
            logger.warning(
                "[MCP_SERVE] opentelemetry-instrumentation-mcp NOT installed: memos spans "
                "will export but start a NEW trace instead of nesting under the caller. "
                "Install it (client + server) for end-to-end cross-service traces."
            )
        else:
            try:
                MCPInstrumentor().instrument()
                logger.info(
                    "[MCP_SERVE] MCP instrumentation active — trace context propagates over MCP"
                )
            except Exception as exc:
                logger.warning(
                    "[MCP_SERVE] MCPInstrumentor().instrument() failed: %s", exc
                )

5. src/memos/api/mcp_serve.py (L181-L182)

_bootstrap_telemetry() is called unconditionally in __init__, so every MOSMCPServer instantiation re-runs it. While configure_from_env()configure() is idempotent (guarded by _initialized), the MCPInstrumentor().instrument() call inside _bootstrap_telemetry() is NOT guarded and will execute again on every construction when OTEL_EXPORTER_OTLP_ENDPOINT is set. Repeated calls to instrument() can cause duplicate spans or errors from the OTel auto-instrumentation library.

Guard the bootstrap with a module-level flag so it runs at most once per process.

💡 Suggested Change

Before:

        _bootstrap_telemetry()
        self.mcp = FastMCP("MOS Memory System")

After:

_bootstrap_telemetry_done = False

def _bootstrap_telemetry() -> None:
    global _bootstrap_telemetry_done
    if _bootstrap_telemetry_done:
        return
    _bootstrap_telemetry_done = True
    # ... rest of function unchanged

# In MOSMCPServer.__init__:
        _bootstrap_telemetry()
        self.mcp = FastMCP("MOS Memory System")

6. src/memos/api/server_api.py (L83-L84)

_FastAPIInstrumentor.instrument_app(app) is called whenever the opentelemetry-instrumentation-fastapi package is installed, even when configure_from_env() returned False (i.e., no OTLP endpoint was configured). In that case the instrumentor binds to the global no-op TracerProvider, which means every request still pays the overhead of span creation and context propagation — yet nothing is exported. The intent expressed in the surrounding comments is that instrumentation should only be active when the SDK is actually configured.

Fix: guard the instrumentation call on both conditions.

if _OTEL_FASTAPI_AVAILABLE and _OTEL_CONFIGURED:
    _FastAPIInstrumentor.instrument_app(app)
💡 Suggested Change

Before:

if _OTEL_FASTAPI_AVAILABLE:
    _FastAPIInstrumentor.instrument_app(app)

After:

if _OTEL_FASTAPI_AVAILABLE and _OTEL_CONFIGURED:
    _FastAPIInstrumentor.instrument_app(app)

7. src/memos/mem_os/core.py (L670-L675)

Bug: search_preference_memory passes self.session_id (the instance-level default) instead of target_session_id (the resolved value computed at the top of search). When a caller passes an explicit session_id argument, preference memory will be searched with the wrong session, causing cross-session data leakage or silent miss.

Change self.session_idtarget_session_id.

💡 Suggested Change

Before:

                        memories = cube.pref_mem.search(
                            query,
                            top_k=top_k if top_k else self.config.top_k,
                            info={
                                "user_id": target_user_id,
                                "session_id": self.session_id,

After:

                        memories = cube.pref_mem.search(
                            query,
                            top_k=top_k if top_k else self.config.top_k,
                            info={
                                "user_id": target_user_id,
                                "session_id": target_session_id,

8. src/memos/mem_os/core.py (L590)

The span attribute uses is not None to record the effective top_k, but the inner search functions use a plain truthiness check (top_k if top_k else self.config.top_k). When a caller passes top_k=0, the span records 0 while the actual search silently uses self.config.top_k, making the telemetry data misleading and the top_k=0 path untestable via traces.

Align the span attribute with the actual runtime behaviour by using the same truthiness check, or — better — fix both call sites to use is not None consistently:

effective_top_k = top_k if top_k is not None else self.config.top_k

then reference effective_top_k in _span_attrs and pass it into both search functions.

💡 Suggested Change

Before:

            MEMORY_TOP_K: top_k if top_k is not None else self.config.top_k,

After:

            effective_top_k = top_k if top_k is not None else self.config.top_k
            # … then use effective_top_k in _span_attrs and in both search closures
            MEMORY_TOP_K: effective_top_k,

9. src/memos/telemetry.py (L94)

_item_count_gauge is declared and initialised to None but is never assigned in configure() and never referenced anywhere in this file. It is dead code that will mislead readers into thinking observable gauge / index-size reporting is implemented when it is not. Either implement it or remove the declaration.


10. src/memos/telemetry.py (L174-L179)

_initialized is only set to True at the very end of configure(). If any line before it raises (e.g. a network error during OTLPLogExporter construction, or any future expansion), the function returns with _initialized = False while the tracer provider, meter provider, and possibly the logging handler are already installed globally. A subsequent call would then reinstall another LoggingHandler on the memos root logger, causing every log record to be exported twice.

Fix: set _initialized = True immediately after the guard checks (before touching any global state), or wrap the body in a try/except that rolls back / cleans up on failure.

💡 Suggested Change

Before:

    otel_handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider)
    root_logger = logging.getLogger("memos")
    root_logger.addHandler(otel_handler)
    _otel_logger = logging.getLogger(f"{INSTRUMENT_NAME}.ops")

    _initialized = True

After:

    _initialized = True   # Mark before mutating globals; prevents double-init on partial failure

    otel_handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider)
    root_logger = logging.getLogger("memos")
    root_logger.addHandler(otel_handler)
    _otel_logger = logging.getLogger(f"{INSTRUMENT_NAME}.ops")

11. tests/test_mcp_serve_telemetry.py (L165-L170)

This test mutates the process-global OTel TracerProvider via trace.set_tracer_provider(provider) but never restores it. Any test that runs afterward and relies on the default no-op provider (or checks that _initialized is False) will silently inherit this SDK provider, making the suite order-dependent and potentially flaky in CI.

Restore the global state after the test, for example by saving the previous provider and resetting it, or by using a monkeypatch/fixture teardown:

prev_provider = trace.get_tracer_provider()
trace.set_tracer_provider(provider)
try:
    ...
finally:
    trace.set_tracer_provider(prev_provider)

Alternatively, use pytest's monkeypatch.setattr to scope the replacement to the test lifetime:

monkeypatch.setattr(trace, '_TRACER_PROVIDER', provider, raising=False)

12. tests/test_telemetry.py (L35-L41)

telemetry.py declares a fourth metric instrument at module level — _item_count_gauge: metrics.ObservableGauge | None = None — that this fixture never resets. If any test creates it (or the real configure() is invoked), its value leaks into subsequent tests. Add tel._item_count_gauge = None to keep the reset complete.

💡 Suggested Change

Before:

    tel._initialized = False
    tel._tracer = None
    tel._meter = None
    tel._otel_logger = None
    tel._op_counter = None
    tel._latency_histogram = None
    tel._result_count_histogram = None

After:

    tel._initialized = False
    tel._tracer = None
    tel._meter = None
    tel._otel_logger = None
    tel._op_counter = None
    tel._latency_histogram = None
    tel._item_count_gauge = None
    tel._result_count_histogram = None

13. tests/test_telemetry.py (L49-L58)

trace.set_tracer_provider() and metrics.set_meter_provider() mutate the process-wide OTel globals, but the fixture never tears them down or restores the previous providers. This causes cross-test and cross-module state pollution — in particular, tests/test_mcp_serve_telemetry.py::test_memory_span_service_name_is_memos also calls trace.set_tracer_provider() with a different provider, and whichever test runs last leaves a stale global for anything that follows.

Capture the old providers before overwriting, and restore them (plus call shutdown()) in teardown:

old_tracer_provider = trace.get_tracer_provider()
old_meter_provider = metrics.get_meter_provider()
# ... setup ...
yield span_exporter, metric_reader
tracer_provider.shutdown()
meter_provider.shutdown()
trace.set_tracer_provider(old_tracer_provider)
metrics.set_meter_provider(old_meter_provider)
💡 Suggested Change

Before:

    span_exporter = InMemorySpanExporter()
    tracer_provider = TracerProvider()
    tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter))
    trace.set_tracer_provider(tracer_provider)
    tel._tracer = tracer_provider.get_tracer(tel.INSTRUMENT_NAME)

    metric_reader = InMemoryMetricReader()
    meter_provider = MeterProvider(metric_readers=[metric_reader])
    metrics.set_meter_provider(meter_provider)
    tel._meter = meter_provider.get_meter(tel.INSTRUMENT_NAME)

After:

    old_tracer_provider = trace.get_tracer_provider()
    old_meter_provider = metrics.get_meter_provider()

    span_exporter = InMemorySpanExporter()
    tracer_provider = TracerProvider()
    tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter))
    trace.set_tracer_provider(tracer_provider)
    tel._tracer = tracer_provider.get_tracer(tel.INSTRUMENT_NAME)

    metric_reader = InMemoryMetricReader()
    meter_provider = MeterProvider(metric_readers=[metric_reader])
    metrics.set_meter_provider(meter_provider)
    tel._meter = meter_provider.get_meter(tel.INSTRUMENT_NAME)

    tel._op_counter = tel._meter.create_counter("memory.operations")
    tel._latency_histogram = tel._meter.create_histogram("memory.operation.duration")
    tel._result_count_histogram = tel._meter.create_histogram("memory.result.count")
    tel._initialized = True

    yield span_exporter, metric_reader

    tracer_provider.shutdown()
    meter_provider.shutdown()
    trace.set_tracer_provider(old_tracer_provider)
    metrics.set_meter_provider(old_meter_provider)

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (6/6 executed). memos_python_core/changed-repo-python: 6/6. Duration: 1s [advisory, non-gating] AI-generated tests on branch test/auto-gen-dac9d2f6ee0314fb-20260804185426: 181/184 passed, 3 failed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/memory-semconv-otel

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 4, 2026
…mport, lint, test guards

Resolves the Copilot review comments on the upstream PR:
- telemetry.py: guard the opentelemetry imports so `import memos.telemetry`
  (and thus `import memos.mem_os.core`) works on a default install WITHOUT the
  [otel] extra — the instrumentation degrades to a no-op (`_OTEL_AVAILABLE`,
  `_NoopSpan`, guarded configure/configure_from_env/get_tracer/memory_span).
  Drops the unused `get_logger_provider` import; moves `Callable` into a
  TYPE_CHECKING block; simplifies `_resolve_service_version` (no blind except).
- core.py: drop unused telemetry constants (MEMORY_CUBE_ID, MEMORY_ITEM_COUNT,
  TIER_ACTIVATION, TIER_PARAMETRIC) — F401.
- tests: guard both suites with `pytest.importorskip("opentelemetry")` and drop
  unused sys/types imports.

The env bootstrap the reviewer asked for (configure_from_env + FastAPI/MCP
entry-point wiring) is included in this branch (server_api.py / mcp_serve.py).
Verified: 13 tests pass; `import memos.telemetry` succeeds with opentelemetry
absent (no-op); ruff clean on telemetry.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
@Memtensor-AI Memtensor-AI added area:api 云服务 / FastAPI / OpenAPI / MCP area:memcube GeneralMemCube / cube 生命周期 / cube 配置 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 and removed status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 labels Aug 4, 2026
henrikrexed and others added 2 commits August 4, 2026 13:25
…ependencies)

CI's `scripts/check_dependencies.py` installs main deps only (no [otel] extra)
and fails on any top-level import of a non-main module. single_cube.py imported
`from opentelemetry.trace import get_current_span` at module level, so the check
(and thus the "Python tests" workflow) failed.

- telemetry.py: add a no-op-safe `get_current_span()` (returns the active span,
  or _NoopSpan when OTel is absent).
- single_cube.py: import `get_current_span` from `memos.telemetry` (our own
  package, excluded by the check) instead of from `opentelemetry` directly.

Verified: no `src/memos` file has a tree.body top-level `opentelemetry` import
(same AST walk check_dependencies uses); 13 tests pass; telemetry.py ruff clean;
get_current_span works with opentelemetry present (real span) and absent (no-op).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
…check`

The `python-tests` workflow runs `ruff format --check` (ruff 0.11.8). Reflowed
the OTel additions to match: collapse now-short calls onto one line, blank lines
around the nested `_NoopSpan`/decorator defs, and quote/spacing normalization in
the telemetry tests. No behavior change.

Verified with ruff 0.11.8 + repo config: `ruff check` and `ruff format --check`
both clean on all changed files; `check_dependencies.py` finds no top-level
optional imports; 13 telemetry tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (13/13 executed). memos_python_core/changed-repo-python: 13/13. Duration: 1s [advisory, non-gating] AI-generated tests on branch test/auto-gen-6bfc3a89d17f9cb9-20260804194123: 135/139 passed, 4 failed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/memory-semconv-otel

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 4, 2026
…[all]

Root cause of the failing `Python tests` workflow: the PR added the `[otel]`
optional-dependency extra to pyproject.toml but never regenerated poetry.lock,
so `poetry check --lock` failed ("pyproject.toml changed significantly since
poetry.lock was last generated") and CI's strict `poetry install` (poetry 2.1.3)
errored before any test ran.

- poetry.lock: regenerated — now includes opentelemetry-* and transitive deps
  (poetry check --lock passes).
- pyproject.toml: add the opentelemetry packages to the `[all]` extra (they were
  the only sub-extra missing from `all`). CI's pytest step runs
  `poetry install --extras all`, so this makes opentelemetry present during the
  suite → the telemetry tests actually run (instead of pytest.importorskip
  skipping them) and cover telemetry.py, keeping total coverage above
  --cov-fail-under=28.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
@Memtensor-AI Memtensor-AI added status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 and removed status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 labels Aug 4, 2026
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (13/13 executed). memos_python_core/changed-repo-python: 13/13. Duration: 1s [advisory, non-gating] AI-generated tests on branch test/auto-gen-468bce880403ade4-20260804201833: 61/89 passed, 28 failed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/memory-semconv-otel

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:api 云服务 / FastAPI / OpenAPI / MCP area:core MOS 编排层 / 框架底座 / 跨模块问题 area:memcube GeneralMemCube / cube 生命周期 / cube 配置 status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants