diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c96ca356..29e05db55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/agentex-sdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -44,7 +44,7 @@ jobs: permissions: contents: read id-token: write - runs-on: ${{ github.repository == 'stainless-sdks/agentex-sdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -87,7 +87,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/agentex-sdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fa12be2ab..f47357f55 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,4 @@ { - ".": "0.20.0", - "adk": "0.20.0" + ".": "0.21.0", + "adk": "0.21.0" } diff --git a/.stats.yml b/.stats.yml index 2764fd11e..3381aa342 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-b2df5f506330ad5fba5a0d518ab8a4bcf876e8c3684a4fe0d0cc5102fd9c569e.yml -openapi_spec_hash: 132e9efdb0535d9594abadf799431cf5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-132fed556e527565b396688a09e873b51ae42c6ad11757d49fc5ef1f2091c943.yml +openapi_spec_hash: 0c1d8518f9f60bb8dc325e26fe1ebadc config_hash: 593e89b291976a5e84e4c3c3f8324354 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd212e16..0e976c8d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ * **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.21.0 (2026-07-24) + +Full Changelog: [agentex-client-v0.20.0...agentex-client-v0.21.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.20.0...agentex-client-v0.21.0) + +### Features + +* **api:** add include_live parameter to schedules list method ([199fd6a](https://github.com/scaleapi/scale-agentex-python/commit/199fd6a633bc82626a4629ee1ac52a40e6f10271)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([23c1b6b](https://github.com/scaleapi/scale-agentex-python/commit/23c1b6b8dc60f16c5e19a1aab68d244d286caffc)) + ## 0.20.0 (2026-07-16) Full Changelog: [agentex-client-v0.19.0...agentex-client-v0.20.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.19.0...agentex-client-v0.20.0) diff --git a/adk/CHANGELOG.md b/adk/CHANGELOG.md index 76e5d8779..be0f83db9 100644 --- a/adk/CHANGELOG.md +++ b/adk/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.21.0 (2026-07-24) + +Full Changelog: [agentex-sdk-v0.20.0...agentex-sdk-v0.21.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.20.0...agentex-sdk-v0.21.0) + +### Chores + +* **agentex-sdk:** Synchronize agentex versions + ## 0.20.0 (2026-07-16) Full Changelog: [agentex-sdk-v0.19.0...agentex-sdk-v0.20.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.19.0...agentex-sdk-v0.20.0) diff --git a/adk/pyproject.toml b/adk/pyproject.toml index 5fca1d619..808e2945f 100644 --- a/adk/pyproject.toml +++ b/adk/pyproject.toml @@ -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.20.0" +version = "0.21.0" description = "Agent Development Kit (ADK) overlay for the Agentex API — FastACP server, Temporal workflows, LLM provider integrations, observability" license = "Apache-2.0" authors = [ diff --git a/pyproject.toml b/pyproject.toml index 4a3fea3f2..6aa945921 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.20.0" +version = "0.21.0" description = "The official Python REST client for the Agentex API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/agentex/_version.py b/src/agentex/_version.py index a6c7a942b..9bc04e166 100644 --- a/src/agentex/_version.py +++ b/src/agentex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "agentex" -__version__ = "0.20.0" # x-release-please-version +__version__ = "0.21.0" # x-release-please-version diff --git a/src/agentex/lib/core/tracing/obs_ids.py b/src/agentex/lib/core/tracing/obs_ids.py new file mode 100644 index 000000000..1be803a84 --- /dev/null +++ b/src/agentex/lib/core/tracing/obs_ids.py @@ -0,0 +1,83 @@ +"""Correlate adk business spans with the active observability trace. + +The adk business ``trace_id`` is the agent **task id** (run-level: it spans the +whole agent run across many requests -- task/create, then each message/send turn), +so we must NOT overwrite it with a per-request observability trace_id. Doing so +would collapse the run-level grouping. + +Instead, each business span is *tagged* with the active observability +trace_id/span_id (this is the OpenTelemetry "span link" pattern -- correlate +across trace granularities rather than merging them). You can then pivot from a +persisted business span to the Tempo/Datadog trace for the turn that produced it, +while the business trace still groups the entire run by task id. + +Source selection follows SGP_OBS_MODE, matching egp-api-backend: + - unset / "dd_only": ddtrace context (current stack) + - "dual": OTel/LGTM preferred, ddtrace fallback + - "lgtm": OTel/LGTM only + +This never fabricates ids -- if no observability context is active, it returns +an empty dict and the span is simply not tagged. +""" +from __future__ import annotations + +import os +from typing import Dict, Optional, Tuple + +__all__ = ("get_obs_mode", "obs_correlation") + +DD_ONLY = "dd_only" +DUAL = "dual" +LGTM = "lgtm" +_DEFAULT_MODE = DD_ONLY +_VALID_MODES = (DD_ONLY, DUAL, LGTM) + + +def get_obs_mode() -> str: + """Unset/empty/unrecognized -> ``dd_only`` (current behavior).""" + raw = os.getenv("SGP_OBS_MODE") + if not raw: + return _DEFAULT_MODE + mode = raw.strip().lower() + return mode if mode in _VALID_MODES else _DEFAULT_MODE + + +def _lgtm_ids() -> Optional[Tuple[str, str]]: + try: + from opentelemetry import trace + except ImportError: + return None + ctx = trace.get_current_span().get_span_context() + if ctx and ctx.is_valid: + return format(ctx.trace_id, "032x"), format(ctx.span_id, "016x") + return None + + +def _ddtrace_ids() -> Optional[Tuple[str, str]]: + try: + from ddtrace import tracer + except ImportError: + return None + ctx = tracer.current_trace_context() + if ctx and ctx.trace_id: + return format(ctx.trace_id, "032x"), format(ctx.span_id or 0, "016x") + return None + + +def obs_correlation() -> Dict[str, str]: + """Return ``{"obs.trace_id": ..., "obs.span_id": ...}`` for the active + observability context, or ``{}`` if none is active. + + Never fabricates ids -- this is a correlation tag, not the span's id. + """ + mode = get_obs_mode() + if mode == LGTM: + ids = _lgtm_ids() + elif mode == DUAL: + ids = _lgtm_ids() or _ddtrace_ids() + else: # dd_only + ids = _ddtrace_ids() + + if not ids: + return {} + return {"obs.trace_id": ids[0], "obs.span_id": ids[1]} diff --git a/src/agentex/lib/core/tracing/trace.py b/src/agentex/lib/core/tracing/trace.py index a22bfd658..c3ec91bc3 100644 --- a/src/agentex/lib/core/tracing/trace.py +++ b/src/agentex/lib/core/tracing/trace.py @@ -11,6 +11,7 @@ from agentex.types.span import Span from agentex.lib.utils.logging import make_logger from agentex.lib.utils.model_utils import recursive_model_dump +from agentex.lib.core.tracing.obs_ids import obs_correlation from agentex.lib.core.tracing.span_error import set_span_error from agentex.lib.core.tracing.span_queue import ( SpanEventType, @@ -79,6 +80,12 @@ def start_span( serialized_input = recursive_model_dump(input) if input else None serialized_data = recursive_model_dump(data) if data else None + # Tag the business span with the active observability trace_id/span_id + # (OTel/ddtrace) so it can be correlated to the per-turn obs trace. The + # business trace_id stays the run-level task id -- see obs_ids.py. + obs = obs_correlation() + if obs: + serialized_data = {**(serialized_data or {}), **obs} id = str(uuid.uuid4()) span = Span( @@ -229,6 +236,12 @@ async def start_span( serialized_input = recursive_model_dump(input) if input else None serialized_data = recursive_model_dump(data) if data else None + # Tag the business span with the active observability trace_id/span_id + # (OTel/ddtrace) so it can be correlated to the per-turn obs trace. The + # business trace_id stays the run-level task id -- see obs_ids.py. + obs = obs_correlation() + if obs: + serialized_data = {**(serialized_data or {}), **obs} id = str(uuid.uuid4()) span = Span( diff --git a/src/agentex/resources/agents/schedules.py b/src/agentex/resources/agents/schedules.py index e6576b6d7..1750c513b 100644 --- a/src/agentex/resources/agents/schedules.py +++ b/src/agentex/resources/agents/schedules.py @@ -277,6 +277,7 @@ def list( self, agent_id: str, *, + include_live: bool | Omit = omit, limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -289,6 +290,8 @@ def list( List run schedules for an agent. Args: + include_live: Include live Temporal state and upcoming action times. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -306,7 +309,13 @@ def list( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"limit": limit}, schedule_list_params.ScheduleListParams), + query=maybe_transform( + { + "include_live": include_live, + "limit": limit, + }, + schedule_list_params.ScheduleListParams, + ), ), cast_to=ScheduleListResponse, ) @@ -1056,6 +1065,7 @@ async def list( self, agent_id: str, *, + include_live: bool | Omit = omit, limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1068,6 +1078,8 @@ async def list( List run schedules for an agent. Args: + include_live: Include live Temporal state and upcoming action times. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1085,7 +1097,13 @@ async def list( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"limit": limit}, schedule_list_params.ScheduleListParams), + query=await async_maybe_transform( + { + "include_live": include_live, + "limit": limit, + }, + schedule_list_params.ScheduleListParams, + ), ), cast_to=ScheduleListResponse, ) diff --git a/src/agentex/types/agents/schedule_create_response.py b/src/agentex/types/agents/schedule_create_response.py index 4440629a2..9902bb832 100644 --- a/src/agentex/types/agents/schedule_create_response.py +++ b/src/agentex/types/agents/schedule_create_response.py @@ -86,6 +86,12 @@ class ScheduleCreateResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_list_params.py b/src/agentex/types/agents/schedule_list_params.py index 8a1d5561a..6e767e207 100644 --- a/src/agentex/types/agents/schedule_list_params.py +++ b/src/agentex/types/agents/schedule_list_params.py @@ -8,4 +8,7 @@ class ScheduleListParams(TypedDict, total=False): + include_live: bool + """Include live Temporal state and upcoming action times.""" + limit: int diff --git a/src/agentex/types/agents/schedule_list_response.py b/src/agentex/types/agents/schedule_list_response.py index 182bd53eb..faa19527f 100644 --- a/src/agentex/types/agents/schedule_list_response.py +++ b/src/agentex/types/agents/schedule_list_response.py @@ -86,6 +86,12 @@ class RunSchedule(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_pause_by_name_response.py b/src/agentex/types/agents/schedule_pause_by_name_response.py index 4c0ce1061..61232fdde 100644 --- a/src/agentex/types/agents/schedule_pause_by_name_response.py +++ b/src/agentex/types/agents/schedule_pause_by_name_response.py @@ -86,6 +86,12 @@ class SchedulePauseByNameResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_pause_response.py b/src/agentex/types/agents/schedule_pause_response.py index c14c83443..16e4a85ce 100644 --- a/src/agentex/types/agents/schedule_pause_response.py +++ b/src/agentex/types/agents/schedule_pause_response.py @@ -86,6 +86,12 @@ class SchedulePauseResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_resume_by_name_response.py b/src/agentex/types/agents/schedule_resume_by_name_response.py index 6bdbcd329..5999059c0 100644 --- a/src/agentex/types/agents/schedule_resume_by_name_response.py +++ b/src/agentex/types/agents/schedule_resume_by_name_response.py @@ -86,6 +86,12 @@ class ScheduleResumeByNameResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_resume_response.py b/src/agentex/types/agents/schedule_resume_response.py index 907792401..70e6e2aa0 100644 --- a/src/agentex/types/agents/schedule_resume_response.py +++ b/src/agentex/types/agents/schedule_resume_response.py @@ -86,6 +86,12 @@ class ScheduleResumeResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_retrieve_by_name_response.py b/src/agentex/types/agents/schedule_retrieve_by_name_response.py index 31663f41a..7b21ebb0a 100644 --- a/src/agentex/types/agents/schedule_retrieve_by_name_response.py +++ b/src/agentex/types/agents/schedule_retrieve_by_name_response.py @@ -86,6 +86,12 @@ class ScheduleRetrieveByNameResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_retrieve_response.py b/src/agentex/types/agents/schedule_retrieve_response.py index 20375347e..374ac635f 100644 --- a/src/agentex/types/agents/schedule_retrieve_response.py +++ b/src/agentex/types/agents/schedule_retrieve_response.py @@ -86,6 +86,12 @@ class ScheduleRetrieveResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_skip_response.py b/src/agentex/types/agents/schedule_skip_response.py index f4daf514b..f97216e0b 100644 --- a/src/agentex/types/agents/schedule_skip_response.py +++ b/src/agentex/types/agents/schedule_skip_response.py @@ -86,6 +86,12 @@ class ScheduleSkipResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_trigger_by_name_response.py b/src/agentex/types/agents/schedule_trigger_by_name_response.py index 036cf72f3..4006b7ad7 100644 --- a/src/agentex/types/agents/schedule_trigger_by_name_response.py +++ b/src/agentex/types/agents/schedule_trigger_by_name_response.py @@ -86,6 +86,12 @@ class ScheduleTriggerByNameResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_trigger_response.py b/src/agentex/types/agents/schedule_trigger_response.py index 22695f9c0..dbff651b0 100644 --- a/src/agentex/types/agents/schedule_trigger_response.py +++ b/src/agentex/types/agents/schedule_trigger_response.py @@ -86,6 +86,12 @@ class ScheduleTriggerResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_unskip_response.py b/src/agentex/types/agents/schedule_unskip_response.py index e25f31bbe..96f624220 100644 --- a/src/agentex/types/agents/schedule_unskip_response.py +++ b/src/agentex/types/agents/schedule_unskip_response.py @@ -86,6 +86,12 @@ class ScheduleUnskipResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_update_by_name_response.py b/src/agentex/types/agents/schedule_update_by_name_response.py index 8e8fd2112..e2905593c 100644 --- a/src/agentex/types/agents/schedule_update_by_name_response.py +++ b/src/agentex/types/agents/schedule_update_by_name_response.py @@ -86,6 +86,12 @@ class ScheduleUpdateByNameResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/src/agentex/types/agents/schedule_update_response.py b/src/agentex/types/agents/schedule_update_response.py index a27701d21..a7416e776 100644 --- a/src/agentex/types/agents/schedule_update_response.py +++ b/src/agentex/types/agents/schedule_update_response.py @@ -86,6 +86,12 @@ class ScheduleUpdateResponse(BaseModel): last_action_time: Optional[datetime] = None """When the schedule last fired.""" + live_data_available: Optional[bool] = None + """Whether requested live Temporal fields were retrieved successfully. + + Null when live enrichment was not requested. + """ + next_action_times: Optional[List[datetime]] = None """Upcoming scheduled fire times.""" diff --git a/tests/api_resources/agents/test_schedules.py b/tests/api_resources/agents/test_schedules.py index 362d32f77..8b281ee31 100644 --- a/tests/api_resources/agents/test_schedules.py +++ b/tests/api_resources/agents/test_schedules.py @@ -249,6 +249,7 @@ def test_method_list(self, client: Agentex) -> None: def test_method_list_with_all_params(self, client: Agentex) -> None: schedule = client.agents.schedules.list( agent_id="agent_id", + include_live=True, limit=1, ) assert_matches_type(ScheduleListResponse, schedule, path=["response"]) @@ -1205,6 +1206,7 @@ async def test_method_list(self, async_client: AsyncAgentex) -> None: async def test_method_list_with_all_params(self, async_client: AsyncAgentex) -> None: schedule = await async_client.agents.schedules.list( agent_id="agent_id", + include_live=True, limit=1, ) assert_matches_type(ScheduleListResponse, schedule, path=["response"])