Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
".": "0.26.0",
"adk": "0.26.0"
".": "0.27.0",
"adk": "0.27.0"
}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@

* **tracing:** emit OTel metrics for async span queue depth, batch drain, and SGP export success/failure (HTTP status labels). Disable SDK-side recording with ``AGENTEX_TRACING_METRICS=0``.

## 0.27.0 (2026-09-16)

Full Changelog: [agentex-client-v0.26.0...agentex-client-v0.27.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.26.0...agentex-client-v0.27.0)

### Features

* **obs:** wire sgp-obs from the SDK for traces, metrics and logs ([#518](https://github.com/scaleapi/scale-agentex-python/issues/518)) ([d88fac6](https://github.com/scaleapi/scale-agentex-python/commit/d88fac6a0b3e572a6346106a615265b2354d1f79))
* **registration:** report the agent's commit and source repo at registration ([#508](https://github.com/scaleapi/scale-agentex-python/issues/508)) ([94335d7](https://github.com/scaleapi/scale-agentex-python/commit/94335d71ece7bfff3ba684fe43991e4bd2397295))
* **tracing:** stamp __commit_sha__ automatically when AGENT_COMMIT_SHA is set ([#507](https://github.com/scaleapi/scale-agentex-python/issues/507)) ([53ab900](https://github.com/scaleapi/scale-agentex-python/commit/53ab9007ab2a78528c38fe254929b92ccce740a3))


### Reverts

* **obs:** remove sgp-obs beta changes ([#522](https://github.com/scaleapi/scale-agentex-python/issues/522)) ([687ebfb](https://github.com/scaleapi/scale-agentex-python/commit/687ebfbf874e1feb01e102c7130cebb7f26e387a))

## 0.26.0 (2026-09-14)

Full Changelog: [agentex-client-v0.25.0...agentex-client-v0.26.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.25.0...agentex-client-v0.26.0)
Expand Down
13 changes: 13 additions & 0 deletions adk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.27.0 (2026-09-16)

Full Changelog: [agentex-sdk-v0.26.0...agentex-sdk-v0.27.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.26.0...agentex-sdk-v0.27.0)

### Features

* **obs:** wire sgp-obs from the SDK for traces, metrics and logs ([#518](https://github.com/scaleapi/scale-agentex-python/issues/518)) ([d88fac6](https://github.com/scaleapi/scale-agentex-python/commit/d88fac6a0b3e572a6346106a615265b2354d1f79))


### Reverts

* **obs:** remove sgp-obs beta changes ([#522](https://github.com/scaleapi/scale-agentex-python/issues/522)) ([687ebfb](https://github.com/scaleapi/scale-agentex-python/commit/687ebfbf874e1feb01e102c7130cebb7f26e387a))

## 0.26.0 (2026-09-14)

Full Changelog: [agentex-sdk-v0.25.0...agentex-sdk-v0.26.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.25.0...agentex-sdk-v0.26.0)
Expand Down
2 changes: 1 addition & 1 deletion adk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# (agentex/{__init__.py, _*.py, types/, resources/}) ships from the slim
# sibling package `agentex-client` which is pinned as a runtime dep.
name = "agentex-sdk"
version = "0.26.0"
version = "0.27.0"
description = "Agent Development Kit (ADK) overlay for the Agentex API — FastACP server, Temporal workflows, LLM provider integrations, observability"
license = "Apache-2.0"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# overlay (formerly `src/agentex/lib/*`) now lives in `adk/` and ships
# as the sibling `agentex-sdk` package — see `adk/pyproject.toml`.
name = "agentex-client"
version = "0.26.0"
version = "0.27.0"
description = "The official Python REST client for the Agentex API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/agentex/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "agentex"
__version__ = "0.26.0" # x-release-please-version
__version__ = "0.27.0" # x-release-please-version
35 changes: 27 additions & 8 deletions src/agentex/lib/core/tracing/code_revision.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Opt-in stamping of the agent's source commit onto its spans.
"""Stamping of the agent's source commit onto its spans.

Nothing is stamped until the agent calls :func:`enable`, mirroring the
``lineage`` registry next door: a process-wide switch the agent sets once at
import, rather than automatic behaviour every agent inherits. When enabled the
resolved commit lands in span data under ``__commit_sha__`` and is searchable in
the SGP Traces UI as ``__commit_sha__:<sha>``.
Stamping turns on when the process starts with ``AGENT_COMMIT_SHA`` set, which
the SGP cloud deploy does from the build record's attested commit, or when the
agent calls :func:`enable` itself. Nothing is stamped otherwise: upgrading the
SDK alone never starts emitting the field. When on, the resolved commit lands in
span data under ``__commit_sha__`` and is searchable in the SGP Traces UI as
``__commit_sha__:<sha>``.

This is deliberately separate from ``__agent_version__``, which is automatic and
carries the deployed image tag verbatim ("image tag or git sha"). That tag is a
Expand All @@ -21,7 +22,7 @@

from agentex.lib.utils.logging import make_logger

__all__ = ("COMMIT_SHA_KEY", "enable", "disable", "is_enabled", "commit_sha")
__all__ = ("COMMIT_SHA_KEY", "enable", "disable", "is_enabled", "commit_sha", "is_git_object_name")

logger = make_logger(__name__)

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


def is_git_object_name(value: str) -> bool:
"""Whether ``value`` is a full or abbreviated git SHA-1/SHA-256 object name."""
return _GIT_SHA_RE.fullmatch(value.strip()) is not None


_COMMIT_SHA_ENV = "AGENT_COMMIT_SHA"
# Fallback only: automatic, and only usable when it happens to be SHA-shaped.
_AGENT_VERSION_ENV = "AGENT_VERSION"
Expand All @@ -42,13 +49,16 @@


def enable(commit_sha: str | None = None) -> None:
"""Opt this process in to stamping ``__commit_sha__`` onto every span.
"""Turn on stamping ``__commit_sha__`` onto every span from this process.

Value precedence: the explicit ``commit_sha`` argument, else
``AGENT_COMMIT_SHA``, else ``AGENT_VERSION`` when the deployment happened to
set it to a bare commit SHA. A value that is not a git object name is
refused with a warning and leaves stamping off -- better an absent field
than one named for a commit that holds an image tag.

Called once at import when ``AGENT_COMMIT_SHA`` is set, so a deployment that
supplies the commit needs no code change in the agent.
"""
global _commit_sha

Expand Down Expand Up @@ -103,3 +113,12 @@ def is_enabled() -> bool:
def commit_sha() -> str | None:
"""The resolved commit SHA, or ``None`` when stamping is not enabled."""
return _commit_sha


def _enable_from_environment() -> None:
"""Auto-enable on ``AGENT_COMMIT_SHA`` only; ``AGENT_VERSION`` stays an explicit fallback."""
if os.environ.get(_COMMIT_SHA_ENV, "").strip():
enable()


_enable_from_environment()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 _enable_from_environment() runs once when code_revision is imported. Normal worker startup imports register_agent before EnvironmentVariables.refresh() loads supported .env and .env.local files. A valid AGENT_COMMIT_SHA from those files reaches registration but never enables tracing. Run the hook after refresh, or read the value lazily.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/core/tracing/code_revision.py
Line: 124

Comment:
`_enable_from_environment()` runs once when `code_revision` is imported. Normal worker startup imports `register_agent` before `EnvironmentVariables.refresh()` loads supported `.env` and `.env.local` files. A valid `AGENT_COMMIT_SHA` from those files reaches registration but never enables tracing. Run the hook after refresh, or read the value lazily.

**Knowledge Base Used:**
- [Observability](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/observability.md)
- [Agent runtime](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/agent-runtime.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

10 changes: 5 additions & 5 deletions src/agentex/lib/environment_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class EnvVarKeys(str, Enum):
AGENT_ID = "AGENT_ID"
AGENT_VERSION = "AGENT_VERSION"
AGENT_COMMIT_SHA = "AGENT_COMMIT_SHA"
AGENT_SOURCE_REPO = "AGENT_SOURCE_REPO"
AGENT_API_KEY = "AGENT_API_KEY"
# ACP Configuration
ACP_URL = "ACP_URL"
Expand Down Expand Up @@ -68,12 +69,11 @@ class EnvironmentVariables(BaseModel):
AGENT_ID: str | None = None
# Build/version discriminator (image tag or git sha), set by the deployment
AGENT_VERSION: str | None = None
# The agent's source commit, baked into the image or set by the deployment.
# Unlike AGENT_VERSION this is expected to be a git SHA and nothing else, and
# it is OPT-IN: nothing is stamped unless the agent calls
# `adk.code_revision.enable()`, which also refuses a value that is not a git
# object name. See agentex.lib.core.tracing.code_revision.
# The agent's source commit, set by the deployment or baked into the image; a git
# SHA and nothing else. Stamped as __commit_sha__ when set (see tracing.code_revision).
AGENT_COMMIT_SHA: str | None = None
# Git remote the agent was built from (any URL form; normalized to host/path on use).
AGENT_SOURCE_REPO: str | None = None
AGENT_API_KEY: str | None = None
ACP_TYPE: str | None = "async"
AGENT_INPUT_TYPE: str | None = None
Expand Down
3 changes: 2 additions & 1 deletion src/agentex/lib/utils/build_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def normalize_remote(url: Optional[str]) -> Optional[str]:
"""Strip credentials and scheme from a remote, returning ``host/path``."""
if not url:
return None
candidate = url.strip()
# Query strings and fragments never name a repo, but they do carry tokens.
candidate = url.strip().split("?", 1)[0].split("#", 1)[0]
# scp-like syntax: git@host:org/repo(.git) — no scheme, host/path split on ':'
if "://" not in candidate and ":" in candidate and "/" not in candidate.split(":", 1)[0]:
candidate = candidate.split("@", 1)[-1].replace(":", "/", 1)
Expand Down
33 changes: 26 additions & 7 deletions src/agentex/lib/utils/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from agentex.lib.utils.logging import make_logger
from agentex.lib.environment_variables import EnvironmentVariables
from agentex.lib.utils.build_provenance import normalize_remote
from agentex.lib.core.tracing.code_revision import is_git_object_name

logger = make_logger(__name__)

Expand All @@ -20,6 +22,29 @@ def get_auth_principal(env_vars: EnvironmentVariables):
except Exception:
return None


def build_registration_metadata(env_vars: EnvironmentVariables, agent_card=None) -> dict:
"""Deployment id, source provenance, and agent card; keys appear only when known."""
metadata: dict = {}
if env_vars.AGENTEX_DEPLOYMENT_ID:
metadata["deployment_id"] = env_vars.AGENTEX_DEPLOYMENT_ID
commit = (env_vars.AGENT_COMMIT_SHA or "").strip()
if commit:
if is_git_object_name(commit):
metadata["commit_sha"] = commit
else:
logger.warning(
"AGENT_COMMIT_SHA=%r is not a git commit SHA; commit_sha omitted from registration.",
commit,
)
repo = normalize_remote(env_vars.AGENT_SOURCE_REPO)
if repo:
metadata["source_repo"] = repo
if agent_card is not None:
metadata["agent_card"] = agent_card.model_dump() if hasattr(agent_card, "model_dump") else agent_card
return metadata


async def register_agent(env_vars: EnvironmentVariables, agent_card=None):
"""Register this agent with the Agentex server"""
if not env_vars.AGENTEX_BASE_URL:
Expand All @@ -33,13 +58,7 @@ async def register_agent(env_vars: EnvironmentVariables, agent_card=None):
or f"Generic description for agent: {env_vars.AGENT_NAME}"
)

# Registration metadata carries the deployment id and agent card.
registration_metadata: dict = {}
if env_vars.AGENTEX_DEPLOYMENT_ID:
registration_metadata["deployment_id"] = env_vars.AGENTEX_DEPLOYMENT_ID
if agent_card is not None:
card_data = agent_card.model_dump() if hasattr(agent_card, "model_dump") else agent_card
registration_metadata["agent_card"] = card_data
registration_metadata = build_registration_metadata(env_vars, agent_card)

# Prepare registration data
registration_data = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ def test_agent_identity_and_version_stamped_into_span_data(self):

SHA = "b362b171a9c4e1f09d8e7a6b5c4d3e2f1a0b9c8d"

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

monkeypatch.setenv("AGENT_COMMIT_SHA", self.SHA)
monkeypatch.delenv("AGENT_COMMIT_SHA", raising=False)
code_revision.disable()

span = _make_span(); span.data = {}
Expand Down
34 changes: 27 additions & 7 deletions tests/lib/core/tracing/test_code_revision.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Opt-in commit-SHA stamping.
"""Commit-SHA stamping.

The contract that matters: an agent that does not call ``enable()`` gets nothing,
so upgrading the SDK never starts emitting this field on its own.
The contract that matters: with ``AGENT_COMMIT_SHA`` absent and no ``enable()``
call, nothing is stamped, so upgrading the SDK never starts emitting this field
on its own. A deployment that sets the env var turns it on without agent code.
"""

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


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

def test_env_set_at_startup_enables_without_a_call(self, monkeypatch):
"""The cloud deploy sets AGENT_COMMIT_SHA from the build record; the agent
should not need to know."""
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
code_revision._enable_from_environment()
assert code_revision.commit_sha() == SHA

def test_env_set_after_import_needs_enable(self, monkeypatch):
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
assert code_revision.commit_sha() is None
code_revision.enable()
assert code_revision.commit_sha() == SHA

def test_bad_env_at_startup_leaves_it_off(self, monkeypatch):
monkeypatch.setenv("AGENT_COMMIT_SHA", "latest")
code_revision._enable_from_environment()
assert code_revision.commit_sha() is None

def test_enable_reads_agent_commit_sha(self, monkeypatch):
monkeypatch.setenv("AGENT_COMMIT_SHA", SHA)
code_revision.enable()
Expand Down
2 changes: 2 additions & 0 deletions tests/lib/test_agent_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ def mock_env_vars(self):
"AGENT_ID": None,
"AGENT_INPUT_TYPE": None,
"AGENT_API_KEY": None,
"AGENT_COMMIT_SHA": None,
"AGENT_SOURCE_REPO": None,
"AGENTEX_DEPLOYMENT_ID": None,
})()
return mock
Expand Down
2 changes: 2 additions & 0 deletions tests/lib/test_agentex_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ def _env_vars_mock():
env.AGENTEX_DEPLOYMENT_ID = None
env.AGENT_ID = None
env.AGENT_INPUT_TYPE = None
env.AGENT_COMMIT_SHA = None
env.AGENT_SOURCE_REPO = None
return env

@staticmethod
Expand Down
1 change: 1 addition & 0 deletions tests/lib/test_build_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def _write(root: Path, rel: str, content: str = "x") -> None:
("https://github.com/scaleapi/Repo.git", "github.com/scaleapi/Repo"),
("https://x-token:secret@GitHub.com/scaleapi/Repo", "github.com/scaleapi/Repo"),
("ssh://git@gitlab.com/group/sub/proj.git", "gitlab.com/group/sub/proj"),
("https://github.com/scaleapi/Repo.git?access_token=SECRET#frag", "github.com/scaleapi/Repo"),
("", None),
(None, None),
],
Expand Down
49 changes: 49 additions & 0 deletions tests/lib/utils/test_registration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Registration metadata: what an agent reports about itself at startup."""

from __future__ import annotations

import pytest

from agentex.lib.utils.registration import build_registration_metadata
from agentex.lib.environment_variables import EnvironmentVariables

SHA = "b362b171a9c4e1f09d8e7a6b5c4d3e2f1a0b9c8d"


def _env(**overrides) -> EnvironmentVariables:
return EnvironmentVariables(AGENT_NAME="sample-agent", ACP_URL="http://agent", **overrides)


def test_nothing_known_yields_empty_metadata():
assert build_registration_metadata(_env()) == {}


def test_commit_and_repo_reported_when_set():
env = _env(AGENT_COMMIT_SHA=SHA, AGENT_SOURCE_REPO="git@github.com:scaleapi/Demo.git")
assert build_registration_metadata(env) == {
"commit_sha": SHA,
"source_repo": "github.com/scaleapi/Demo",
}


@pytest.mark.parametrize("value", ["latest", "v1.2.3", "rocket_mock_agent-" + SHA, "abc", " "])
def test_non_commit_values_are_omitted_not_forwarded(value):
"""A field named for a commit never holds an image tag, same rule as __commit_sha__."""
assert "commit_sha" not in build_registration_metadata(_env(AGENT_COMMIT_SHA=value))


def test_repo_normalization_strips_scheme_and_credentials():
env = _env(AGENT_SOURCE_REPO="https://x-token:secret@GitHub.com/scaleapi/Demo.git")
assert build_registration_metadata(env)["source_repo"] == "github.com/scaleapi/Demo"


def test_deployment_id_and_agent_card_still_reported():
class Card:
def model_dump(self):
return {"name": "sample"}

env = _env(AGENTEX_DEPLOYMENT_ID="dep-1")
assert build_registration_metadata(env, Card()) == {
"deployment_id": "dep-1",
"agent_card": {"name": "sample"},
}
Loading