Skip to content

Commit 7d85e4d

Browse files
Merge branch 'next' into stephen/agentex-sdk-genai-metrics
2 parents f963ea5 + 53ab900 commit 7d85e4d

10 files changed

Lines changed: 145 additions & 32 deletions

File tree

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
"""Opt-in stamping of the agent's source commit onto its spans.
1+
"""Stamping of the agent's source commit onto its spans.
22
3-
Nothing is stamped until the agent calls :func:`enable`, mirroring the
4-
``lineage`` registry next door: a process-wide switch the agent sets once at
5-
import, rather than automatic behaviour every agent inherits. When enabled the
6-
resolved commit lands in span data under ``__commit_sha__`` and is searchable in
7-
the SGP Traces UI as ``__commit_sha__:<sha>``.
3+
Stamping turns on when the process starts with ``AGENT_COMMIT_SHA`` set, which
4+
the SGP cloud deploy does from the build record's attested commit, or when the
5+
agent calls :func:`enable` itself. Nothing is stamped otherwise: upgrading the
6+
SDK alone never starts emitting the field. When on, the resolved commit lands in
7+
span data under ``__commit_sha__`` and is searchable in the SGP Traces UI as
8+
``__commit_sha__:<sha>``.
89
910
This is deliberately separate from ``__agent_version__``, which is automatic and
1011
carries the deployed image tag verbatim ("image tag or git sha"). That tag is a
@@ -21,7 +22,7 @@
2122

2223
from agentex.lib.utils.logging import make_logger
2324

24-
__all__ = ("COMMIT_SHA_KEY", "enable", "disable", "is_enabled", "commit_sha")
25+
__all__ = ("COMMIT_SHA_KEY", "enable", "disable", "is_enabled", "commit_sha", "is_git_object_name")
2526

2627
logger = make_logger(__name__)
2728

@@ -31,6 +32,12 @@
3132
# git's own 7-character minimum.
3233
_GIT_SHA_RE = re.compile(r"[0-9a-fA-F]{7,64}")
3334

35+
36+
def is_git_object_name(value: str) -> bool:
37+
"""Whether ``value`` is a full or abbreviated git SHA-1/SHA-256 object name."""
38+
return _GIT_SHA_RE.fullmatch(value.strip()) is not None
39+
40+
3441
_COMMIT_SHA_ENV = "AGENT_COMMIT_SHA"
3542
# Fallback only: automatic, and only usable when it happens to be SHA-shaped.
3643
_AGENT_VERSION_ENV = "AGENT_VERSION"
@@ -42,13 +49,16 @@
4249

4350

4451
def enable(commit_sha: str | None = None) -> None:
45-
"""Opt this process in to stamping ``__commit_sha__`` onto every span.
52+
"""Turn on stamping ``__commit_sha__`` onto every span from this process.
4653
4754
Value precedence: the explicit ``commit_sha`` argument, else
4855
``AGENT_COMMIT_SHA``, else ``AGENT_VERSION`` when the deployment happened to
4956
set it to a bare commit SHA. A value that is not a git object name is
5057
refused with a warning and leaves stamping off -- better an absent field
5158
than one named for a commit that holds an image tag.
59+
60+
Called once at import when ``AGENT_COMMIT_SHA`` is set, so a deployment that
61+
supplies the commit needs no code change in the agent.
5262
"""
5363
global _commit_sha
5464

@@ -103,3 +113,12 @@ def is_enabled() -> bool:
103113
def commit_sha() -> str | None:
104114
"""The resolved commit SHA, or ``None`` when stamping is not enabled."""
105115
return _commit_sha
116+
117+
118+
def _enable_from_environment() -> None:
119+
"""Auto-enable on ``AGENT_COMMIT_SHA`` only; ``AGENT_VERSION`` stays an explicit fallback."""
120+
if os.environ.get(_COMMIT_SHA_ENV, "").strip():
121+
enable()
122+
123+
124+
_enable_from_environment()

‎src/agentex/lib/environment_variables.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class EnvVarKeys(str, Enum):
2626
AGENT_ID = "AGENT_ID"
2727
AGENT_VERSION = "AGENT_VERSION"
2828
AGENT_COMMIT_SHA = "AGENT_COMMIT_SHA"
29+
AGENT_SOURCE_REPO = "AGENT_SOURCE_REPO"
2930
AGENT_API_KEY = "AGENT_API_KEY"
3031
# ACP Configuration
3132
ACP_URL = "ACP_URL"
@@ -68,12 +69,11 @@ class EnvironmentVariables(BaseModel):
6869
AGENT_ID: str | None = None
6970
# Build/version discriminator (image tag or git sha), set by the deployment
7071
AGENT_VERSION: str | None = None
71-
# The agent's source commit, baked into the image or set by the deployment.
72-
# Unlike AGENT_VERSION this is expected to be a git SHA and nothing else, and
73-
# it is OPT-IN: nothing is stamped unless the agent calls
74-
# `adk.code_revision.enable()`, which also refuses a value that is not a git
75-
# object name. See agentex.lib.core.tracing.code_revision.
72+
# The agent's source commit, set by the deployment or baked into the image; a git
73+
# SHA and nothing else. Stamped as __commit_sha__ when set (see tracing.code_revision).
7674
AGENT_COMMIT_SHA: str | None = None
75+
# Git remote the agent was built from (any URL form; normalized to host/path on use).
76+
AGENT_SOURCE_REPO: str | None = None
7777
AGENT_API_KEY: str | None = None
7878
ACP_TYPE: str | None = "async"
7979
AGENT_INPUT_TYPE: str | None = None

‎src/agentex/lib/utils/build_provenance.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ def normalize_remote(url: Optional[str]) -> Optional[str]:
8282
"""Strip credentials and scheme from a remote, returning ``host/path``."""
8383
if not url:
8484
return None
85-
candidate = url.strip()
85+
# Query strings and fragments never name a repo, but they do carry tokens.
86+
candidate = url.strip().split("?", 1)[0].split("#", 1)[0]
8687
# scp-like syntax: git@host:org/repo(.git) — no scheme, host/path split on ':'
8788
if "://" not in candidate and ":" in candidate and "/" not in candidate.split(":", 1)[0]:
8889
candidate = candidate.split("@", 1)[-1].replace(":", "/", 1)

‎src/agentex/lib/utils/registration.py‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from agentex.lib.utils.logging import make_logger
99
from agentex.lib.environment_variables import EnvironmentVariables
10+
from agentex.lib.utils.build_provenance import normalize_remote
11+
from agentex.lib.core.tracing.code_revision import is_git_object_name
1012

1113
logger = make_logger(__name__)
1214

@@ -20,6 +22,29 @@ def get_auth_principal(env_vars: EnvironmentVariables):
2022
except Exception:
2123
return None
2224

25+
26+
def build_registration_metadata(env_vars: EnvironmentVariables, agent_card=None) -> dict:
27+
"""Deployment id, source provenance, and agent card; keys appear only when known."""
28+
metadata: dict = {}
29+
if env_vars.AGENTEX_DEPLOYMENT_ID:
30+
metadata["deployment_id"] = env_vars.AGENTEX_DEPLOYMENT_ID
31+
commit = (env_vars.AGENT_COMMIT_SHA or "").strip()
32+
if commit:
33+
if is_git_object_name(commit):
34+
metadata["commit_sha"] = commit
35+
else:
36+
logger.warning(
37+
"AGENT_COMMIT_SHA=%r is not a git commit SHA; commit_sha omitted from registration.",
38+
commit,
39+
)
40+
repo = normalize_remote(env_vars.AGENT_SOURCE_REPO)
41+
if repo:
42+
metadata["source_repo"] = repo
43+
if agent_card is not None:
44+
metadata["agent_card"] = agent_card.model_dump() if hasattr(agent_card, "model_dump") else agent_card
45+
return metadata
46+
47+
2348
async def register_agent(env_vars: EnvironmentVariables, agent_card=None):
2449
"""Register this agent with the Agentex server"""
2550
if not env_vars.AGENTEX_BASE_URL:
@@ -33,13 +58,7 @@ async def register_agent(env_vars: EnvironmentVariables, agent_card=None):
3358
or f"Generic description for agent: {env_vars.AGENT_NAME}"
3459
)
3560

36-
# Registration metadata carries the deployment id and agent card.
37-
registration_metadata: dict = {}
38-
if env_vars.AGENTEX_DEPLOYMENT_ID:
39-
registration_metadata["deployment_id"] = env_vars.AGENTEX_DEPLOYMENT_ID
40-
if agent_card is not None:
41-
card_data = agent_card.model_dump() if hasattr(agent_card, "model_dump") else agent_card
42-
registration_metadata["agent_card"] = card_data
61+
registration_metadata = build_registration_metadata(env_vars, agent_card)
4362

4463
# Prepare registration data
4564
registration_data = {

‎tests/lib/core/tracing/processors/test_sgp_tracing_processor.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ def test_agent_identity_and_version_stamped_into_span_data(self):
5656

5757
SHA = "b362b171a9c4e1f09d8e7a6b5c4d3e2f1a0b9c8d"
5858

59-
def test_commit_sha_is_not_stamped_without_opt_in(self, monkeypatch):
60-
"""Upgrading the SDK must not start emitting __commit_sha__ on its own,
61-
even when the environment carries a perfectly good SHA."""
59+
def test_commit_sha_is_not_stamped_when_env_absent(self, monkeypatch):
60+
"""Upgrading the SDK must not start emitting __commit_sha__ on its own;
61+
only AGENT_COMMIT_SHA or an enable() call turns it on."""
6262
from agentex.lib.core.tracing import code_revision
6363
from agentex.lib.core.tracing.processors.sgp_tracing_processor import _sgp_metadata
6464

65-
monkeypatch.setenv("AGENT_COMMIT_SHA", self.SHA)
65+
monkeypatch.delenv("AGENT_COMMIT_SHA", raising=False)
6666
code_revision.disable()
6767

6868
span = _make_span(); span.data = {}

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
"""Opt-in commit-SHA stamping.
1+
"""Commit-SHA stamping.
22
3-
The contract that matters: an agent that does not call ``enable()`` gets nothing,
4-
so upgrading the SDK never starts emitting this field on its own.
3+
The contract that matters: with ``AGENT_COMMIT_SHA`` absent and no ``enable()``
4+
call, nothing is stamped, so upgrading the SDK never starts emitting this field
5+
on its own. A deployment that sets the env var turns it on without agent code.
56
"""
67

78
from __future__ import annotations
@@ -21,14 +22,33 @@ def _reset():
2122
code_revision.disable()
2223

2324

24-
class TestOptIn:
25-
def test_disabled_by_default(self, monkeypatch):
26-
"""Even with the env fully populated, nothing resolves until enable()."""
27-
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
25+
class TestEnablement:
26+
def test_off_when_env_absent(self, monkeypatch):
27+
"""The import-time hook ignores AGENT_VERSION; that fallback needs enable()."""
28+
monkeypatch.delenv("AGENT_COMMIT_SHA", raising=False)
2829
monkeypatch.setenv("AGENT_VERSION", SHA)
30+
code_revision._enable_from_environment()
2931
assert code_revision.commit_sha() is None
3032
assert code_revision.is_enabled() is False
3133

34+
def test_env_set_at_startup_enables_without_a_call(self, monkeypatch):
35+
"""The cloud deploy sets AGENT_COMMIT_SHA from the build record; the agent
36+
should not need to know."""
37+
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
38+
code_revision._enable_from_environment()
39+
assert code_revision.commit_sha() == SHA
40+
41+
def test_env_set_after_import_needs_enable(self, monkeypatch):
42+
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
43+
assert code_revision.commit_sha() is None
44+
code_revision.enable()
45+
assert code_revision.commit_sha() == SHA
46+
47+
def test_bad_env_at_startup_leaves_it_off(self, monkeypatch):
48+
monkeypatch.setenv("AGENT_COMMIT_SHA", "latest")
49+
code_revision._enable_from_environment()
50+
assert code_revision.commit_sha() is None
51+
3252
def test_enable_reads_agent_commit_sha(self, monkeypatch):
3353
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
3454
code_revision.enable()

‎tests/lib/test_agent_card.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ def mock_env_vars(self):
377377
"AGENT_ID": None,
378378
"AGENT_INPUT_TYPE": None,
379379
"AGENT_API_KEY": None,
380+
"AGENT_COMMIT_SHA": None,
381+
"AGENT_SOURCE_REPO": None,
380382
"AGENTEX_DEPLOYMENT_ID": None,
381383
})()
382384
return mock

‎tests/lib/test_agentex_worker.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ def _env_vars_mock():
140140
env.AGENTEX_DEPLOYMENT_ID = None
141141
env.AGENT_ID = None
142142
env.AGENT_INPUT_TYPE = None
143+
env.AGENT_COMMIT_SHA = None
144+
env.AGENT_SOURCE_REPO = None
143145
return env
144146

145147
@staticmethod

‎tests/lib/test_build_provenance.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def _write(root: Path, rel: str, content: str = "x") -> None:
5050
("https://github.com/scaleapi/Repo.git", "github.com/scaleapi/Repo"),
5151
("https://x-token:secret@GitHub.com/scaleapi/Repo", "github.com/scaleapi/Repo"),
5252
("ssh://git@gitlab.com/group/sub/proj.git", "gitlab.com/group/sub/proj"),
53+
("https://github.com/scaleapi/Repo.git?access_token=SECRET#frag", "github.com/scaleapi/Repo"),
5354
("", None),
5455
(None, None),
5556
],
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Registration metadata: what an agent reports about itself at startup."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from agentex.lib.utils.registration import build_registration_metadata
8+
from agentex.lib.environment_variables import EnvironmentVariables
9+
10+
SHA = "b362b171a9c4e1f09d8e7a6b5c4d3e2f1a0b9c8d"
11+
12+
13+
def _env(**overrides) -> EnvironmentVariables:
14+
return EnvironmentVariables(AGENT_NAME="sample-agent", ACP_URL="http://agent", **overrides)
15+
16+
17+
def test_nothing_known_yields_empty_metadata():
18+
assert build_registration_metadata(_env()) == {}
19+
20+
21+
def test_commit_and_repo_reported_when_set():
22+
env = _env(AGENT_COMMIT_SHA=SHA, AGENT_SOURCE_REPO="git@github.com:scaleapi/Demo.git")
23+
assert build_registration_metadata(env) == {
24+
"commit_sha": SHA,
25+
"source_repo": "github.com/scaleapi/Demo",
26+
}
27+
28+
29+
@pytest.mark.parametrize("value", ["latest", "v1.2.3", "rocket_mock_agent-" + SHA, "abc", " "])
30+
def test_non_commit_values_are_omitted_not_forwarded(value):
31+
"""A field named for a commit never holds an image tag, same rule as __commit_sha__."""
32+
assert "commit_sha" not in build_registration_metadata(_env(AGENT_COMMIT_SHA=value))
33+
34+
35+
def test_repo_normalization_strips_scheme_and_credentials():
36+
env = _env(AGENT_SOURCE_REPO="https://x-token:secret@GitHub.com/scaleapi/Demo.git")
37+
assert build_registration_metadata(env)["source_repo"] == "github.com/scaleapi/Demo"
38+
39+
40+
def test_deployment_id_and_agent_card_still_reported():
41+
class Card:
42+
def model_dump(self):
43+
return {"name": "sample"}
44+
45+
env = _env(AGENTEX_DEPLOYMENT_ID="dep-1")
46+
assert build_registration_metadata(env, Card()) == {
47+
"deployment_id": "dep-1",
48+
"agent_card": {"name": "sample"},
49+
}

0 commit comments

Comments
 (0)