Skip to content

Commit e402a78

Browse files
fix(tracing): narrow Agentex platform ownership
Ignore generic wrappers and database drivers while reserving platform attribution for managed tracing export and Redis stream persistence paths. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 893cb76 commit e402a78

3 files changed

Lines changed: 124 additions & 39 deletions

File tree

‎src/agentex/lib/core/tracing/__init__.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from agentex.lib.core.tracing.span_error import (
55
ERROR_CLASSIFIER_VERSION,
66
AGENTEX_ERROR_CLASSIFIER_CONFIG,
7+
AGENTEX_IGNORED_MODULE_PREFIXES,
8+
AGENTEX_PLATFORM_MODULE_PREFIXES,
79
ErrorCategory,
810
PlatformError,
911
ApplicationError,
@@ -34,6 +36,8 @@
3436
"ErrorClassifierConfig",
3537
"TracebackOwnershipPolicy",
3638
"ERROR_CLASSIFIER_VERSION",
39+
"AGENTEX_IGNORED_MODULE_PREFIXES",
40+
"AGENTEX_PLATFORM_MODULE_PREFIXES",
3741
"AGENTEX_ERROR_CLASSIFIER_CONFIG",
3842
"AsyncSpanQueue",
3943
"get_default_span_queue",

‎src/agentex/lib/core/tracing/span_error.py‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import os
43
from typing import Any, cast
54

65
from scale_gp_beta.lib.tracing import (
@@ -27,11 +26,28 @@
2726
# SGP and agentex-native span stores.
2827
SPAN_ERROR_KEY = "__error__"
2928

30-
_AGENTEX_PACKAGE_ROOT = os.path.normcase(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
29+
AGENTEX_PLATFORM_MODULE_PREFIXES = (
30+
# Trace export is Agentex-owned telemetry delivery, not agent execution.
31+
"agentex.lib.core.tracing.processors",
32+
"agentex.lib.core.tracing.span_queue",
33+
# This repository is the managed Redis stream transport/persistence layer.
34+
"agentex.lib.core.adapters.streams.adapter_redis",
35+
)
36+
AGENTEX_IGNORED_MODULE_PREFIXES = (
37+
# Generic Agentex orchestration/wrappers provide context, not ownership.
38+
"agentex",
39+
# Database/client packages never decide ownership by exception identity.
40+
"sqlalchemy",
41+
"pyodbc",
42+
"psycopg",
43+
"psycopg2",
44+
"asyncpg",
45+
"redis",
46+
)
3147
AGENTEX_ERROR_CLASSIFIER_CONFIG = ErrorClassifierConfig(
3248
policy=TracebackOwnershipPolicy(
33-
platform_module_prefixes=("agentex",),
34-
platform_file_roots=(_AGENTEX_PACKAGE_ROOT,),
49+
platform_module_prefixes=AGENTEX_PLATFORM_MODULE_PREFIXES,
50+
ignored_module_prefixes=AGENTEX_IGNORED_MODULE_PREFIXES,
3551
infer_application_from_unowned_absolute_paths=True,
3652
)
3753
)

‎tests/lib/core/tracing/test_span_error.py‎

Lines changed: 100 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import json
43
import uuid
54
from typing import Any
65
from datetime import UTC, datetime
@@ -44,6 +43,23 @@ def _synthetic_function(module_name: str, filename: str, body: str, **values: An
4443
return namespace["run"]
4544

4645

46+
def _record_synthetic(function: Any, *, innermost_only: bool = False) -> dict[str, Any]:
47+
span = _make_span()
48+
try:
49+
function()
50+
except Exception as exc:
51+
if innermost_only:
52+
traceback = exc.__traceback__
53+
assert traceback is not None
54+
while traceback.tb_next is not None:
55+
traceback = traceback.tb_next
56+
exc = exc.with_traceback(traceback)
57+
set_span_error(span, exc)
58+
error = get_span_error(span)
59+
assert error is not None
60+
return error
61+
62+
4763
# ---------------------------------------------------------------------------
4864
# Helpers: set_span_error / get_span_error
4965
# ---------------------------------------------------------------------------
@@ -102,53 +118,102 @@ class ImplicitlyCategorizedError(RuntimeError):
102118
set_span_error(span, ImplicitlyCategorizedError("boom"))
103119
assert get_span_error(span)["category"] == "unknown" # type: ignore[index]
104120

105-
def test_agentex_internal_origin_is_platform(self):
106-
platform = _synthetic_function(
107-
"agentex.lib.synthetic_runtime",
108-
"/synthetic/agentex/runtime.py",
121+
def test_generic_agentex_wrapper_around_user_failure_is_application(self):
122+
application = _synthetic_function(
123+
"customer_agent.tool",
124+
"/synthetic/application/tool.py",
109125
"raise RuntimeError('boom')",
110126
)
111-
span = _make_span()
112-
try:
113-
platform()
114-
except Exception as exc:
115-
set_span_error(span, exc)
127+
wrapper = _synthetic_function(
128+
"agentex.lib.core.temporal.workflows.workflow",
129+
"/synthetic/agentex/workflow.py",
130+
"target()",
131+
target=application,
132+
)
116133

117-
error = get_span_error(span)
118-
assert error is not None
119-
assert error["category"] == "platform"
134+
error = _record_synthetic(wrapper)
135+
136+
assert error["category"] == "application"
120137
assert error["category_source"] == "stack_trace"
121-
assert error["category_reason"] == "stack_rule:platform_module"
138+
assert error["category_reason"] == "stack_rule:unowned_absolute_source"
122139

123-
def test_stdlib_dependency_under_application_is_application(self):
140+
def test_application_database_failure_is_application(self):
141+
driver = _synthetic_function(
142+
"sqlalchemy.engine",
143+
"/venv/site-packages/sqlalchemy/engine.py",
144+
"raise RuntimeError('db failed')",
145+
)
124146
application = _synthetic_function(
125147
"customer_agent.main",
126148
"/synthetic/application/main.py",
127-
"parse('{')",
128-
parse=json.loads,
149+
"query()",
150+
query=driver,
129151
)
130-
span = _make_span()
131-
try:
132-
application()
133-
except Exception as exc:
134-
set_span_error(span, exc)
135152

136-
assert get_span_error(span)["category"] == "application" # type: ignore[index]
153+
assert _record_synthetic(application)["category"] == "application"
154+
155+
def test_managed_stream_database_failure_is_platform(self):
156+
driver = _synthetic_function(
157+
"redis.asyncio.client",
158+
"/venv/site-packages/redis/client.py",
159+
"raise RuntimeError('db failed')",
160+
)
161+
managed_store = _synthetic_function(
162+
"agentex.lib.core.adapters.streams.adapter_redis",
163+
"/synthetic/agentex/adapter_redis.py",
164+
"query()",
165+
query=driver,
166+
)
137167

138-
def test_stdlib_dependency_under_agentex_is_platform(self):
139-
platform = _synthetic_function(
140-
"agentex.lib.synthetic_runtime",
141-
"/synthetic/agentex/runtime.py",
142-
"parse('{')",
143-
parse=json.loads,
168+
assert _record_synthetic(managed_store)["category"] == "platform"
169+
170+
def test_application_validation_inside_platform_operation_is_application(self):
171+
validation = _synthetic_function(
172+
"customer_agent.query",
173+
"/synthetic/application/query.py",
174+
"raise ValueError('invalid configuration')",
175+
)
176+
managed_store = _synthetic_function(
177+
"agentex.lib.core.adapters.streams.adapter_redis",
178+
"/synthetic/agentex/adapter_redis.py",
179+
"validate()",
180+
validate=validation,
181+
)
182+
183+
assert _record_synthetic(managed_store)["category"] == "application"
184+
185+
def test_driver_only_database_failure_is_unknown(self):
186+
driver = _synthetic_function(
187+
"pyodbc",
188+
"/venv/site-packages/pyodbc.py",
189+
"raise RuntimeError('db failed')",
190+
)
191+
192+
error = _record_synthetic(driver, innermost_only=True)
193+
194+
assert error["category"] == "unknown"
195+
assert error["category_reason"] == "stack_no_owned_frame"
196+
197+
def test_nested_wrapper_dependency_propagation_uses_application(self):
198+
driver = _synthetic_function(
199+
"sqlalchemy.engine",
200+
"/venv/site-packages/sqlalchemy/engine.py",
201+
"raise RuntimeError('db failed')",
202+
)
203+
application = _synthetic_function(
204+
"customer_agent.repository",
205+
"/synthetic/application/repository.py",
206+
"query()",
207+
query=driver,
208+
)
209+
wrapper = _synthetic_function(
210+
"agentex.lib.core.temporal.activities.activity_helpers",
211+
"/synthetic/agentex/activity_helpers.py",
212+
"target()",
213+
target=application,
144214
)
145-
span = _make_span()
146-
try:
147-
platform()
148-
except Exception as exc:
149-
set_span_error(span, exc)
150215

151-
assert get_span_error(span)["category"] == "platform" # type: ignore[index]
216+
assert _record_synthetic(wrapper)["category"] == "application"
152217

153218
def test_set_preserves_existing_dict_keys(self):
154219
span = _make_span(data={"__span_type__": "LLM"})

0 commit comments

Comments
 (0)