Skip to content

Commit 09a816c

Browse files
release: 0.10.5 (#343)
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> Co-authored-by: Alvin Kam <alvin.kam@scale.com>
1 parent 2c59850 commit 09a816c

17 files changed

Lines changed: 433 additions & 55 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.10.4"
2+
".": "0.10.5"
33
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 45
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-c108a179582f0e0c6d479ea4b3bc6310a83693987073967c2b6203df23718eb2.yml
3-
openapi_spec_hash: 53b8e5866709af71bef94816b8ede38b
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-b997afde6595db62caea38bca035fda2812ea52cc8f360dab829b71178e826e6.yml
3+
openapi_spec_hash: d195a98bf64b6edb826bc420773ca52e
44
config_hash: fb079ef7936611b032568661b8165f19

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## 0.10.5 (2026-05-05)
4+
5+
Full Changelog: [v0.10.4...v0.10.5](https://github.com/scaleapi/scale-agentex-python/compare/v0.10.4...v0.10.5)
6+
7+
### Features
8+
9+
* **api:** api update ([ffaecd5](https://github.com/scaleapi/scale-agentex-python/commit/ffaecd5a94b4082f9ef38d5c89286eabf3811759))
10+
* **openai_agents:** expose real `usage`, `response_id`, plumb `previous_response_id`, opt-in `prompt_cache_key` for stateful responses and prompt caching ([#335](https://github.com/scaleapi/scale-agentex-python/issues/335)) ([ba5d64b](https://github.com/scaleapi/scale-agentex-python/commit/ba5d64be1f959ff1a35b30e647a0a5ead21a8402))
11+
12+
13+
### Chores
14+
15+
* **internal:** reformat pyproject.toml ([ba06702](https://github.com/scaleapi/scale-agentex-python/commit/ba06702fd362656d594f73852ad2c690383892a8))
16+
* **internal:** reformat pyproject.toml ([3faf5d5](https://github.com/scaleapi/scale-agentex-python/commit/3faf5d5927abdc3036862d4d06e085cda0eb6cd4))
17+
* **internal:** version bump ([168cc44](https://github.com/scaleapi/scale-agentex-python/commit/168cc44f8199015e232cd2bddf1669a08ee90778))
18+
* **internal:** version bump ([5715828](https://github.com/scaleapi/scale-agentex-python/commit/5715828a358c20b1cc895a696d0c8d803ec71932))
19+
320
## 0.10.4 (2026-05-04)
421

522
Full Changelog: [v0.10.3...v0.10.4](https://github.com/scaleapi/scale-agentex-python/compare/v0.10.3...v0.10.4)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agentex-sdk"
3-
version = "0.10.4"
3+
version = "0.10.5"
44
description = "The official Python library for the agentex API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"

src/agentex/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "agentex"
4-
__version__ = "0.10.4" # x-release-please-version
4+
__version__ = "0.10.5" # x-release-please-version

src/agentex/lib/core/tracing/processors/sgp_tracing_processor.py

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from typing import override
24

35
import scale_gp_beta.lib.tracing as tracing
@@ -125,48 +127,64 @@ def _add_source_to_span(self, span: Span) -> None:
125127

126128
@override
127129
async def on_span_start(self, span: Span) -> None:
128-
self._add_source_to_span(span)
129-
sgp_span = create_span(
130-
name=span.name,
131-
span_type=_get_span_type(span),
132-
span_id=span.id,
133-
parent_id=span.parent_id,
134-
trace_id=span.trace_id,
135-
input=span.input,
136-
output=span.output,
137-
metadata=span.data,
138-
)
139-
sgp_span.start_time = span.start_time.isoformat() # type: ignore[union-attr]
130+
await self.on_spans_start([span])
131+
132+
@override
133+
async def on_span_end(self, span: Span) -> None:
134+
await self.on_spans_end([span])
135+
136+
@override
137+
async def on_spans_start(self, spans: list[Span]) -> None:
138+
if not spans:
139+
return
140+
141+
sgp_spans: list[SGPSpan] = []
142+
for span in spans:
143+
self._add_source_to_span(span)
144+
sgp_span = create_span(
145+
name=span.name,
146+
span_type=_get_span_type(span),
147+
span_id=span.id,
148+
parent_id=span.parent_id,
149+
trace_id=span.trace_id,
150+
input=span.input,
151+
output=span.output,
152+
metadata=span.data,
153+
)
154+
sgp_span.start_time = span.start_time.isoformat() # type: ignore[union-attr]
155+
self._spans[span.id] = sgp_span
156+
sgp_spans.append(sgp_span)
140157

141158
if self.disabled:
142159
logger.warning("SGP is disabled, skipping span upsert")
143160
return
144-
# TODO(AGX1-198): Batch multiple spans into a single upsert_batch call
145-
# instead of one span per HTTP request.
146-
# https://linear.app/scale-epd/issue/AGX1-198/actually-use-sgp-batching-for-spans
147161
await self.sgp_async_client.spans.upsert_batch( # type: ignore[union-attr]
148-
items=[sgp_span.to_request_params()]
162+
items=[s.to_request_params() for s in sgp_spans]
149163
)
150164

151-
self._spans[span.id] = sgp_span
152-
153165
@override
154-
async def on_span_end(self, span: Span) -> None:
155-
sgp_span = self._spans.pop(span.id, None)
156-
if sgp_span is None:
157-
logger.warning(f"Span {span.id} not found in stored spans, skipping span end")
166+
async def on_spans_end(self, spans: list[Span]) -> None:
167+
if not spans:
158168
return
159169

160-
self._add_source_to_span(span)
161-
sgp_span.input = span.input # type: ignore[assignment]
162-
sgp_span.output = span.output # type: ignore[assignment]
163-
sgp_span.metadata = span.data # type: ignore[assignment]
164-
sgp_span.end_time = span.end_time.isoformat() # type: ignore[union-attr]
165-
166-
if self.disabled:
170+
to_upsert: list[SGPSpan] = []
171+
for span in spans:
172+
sgp_span = self._spans.pop(span.id, None)
173+
if sgp_span is None:
174+
logger.warning(f"Span {span.id} not found in stored spans, skipping span end")
175+
continue
176+
177+
self._add_source_to_span(span)
178+
sgp_span.input = span.input # type: ignore[assignment]
179+
sgp_span.output = span.output # type: ignore[assignment]
180+
sgp_span.metadata = span.data # type: ignore[assignment]
181+
sgp_span.end_time = span.end_time.isoformat() # type: ignore[union-attr]
182+
to_upsert.append(sgp_span)
183+
184+
if self.disabled or not to_upsert:
167185
return
168186
await self.sgp_async_client.spans.upsert_batch( # type: ignore[union-attr]
169-
items=[sgp_span.to_request_params()]
187+
items=[s.to_request_params() for s in to_upsert]
170188
)
171189

172190
@override

src/agentex/lib/core/tracing/processors/tracing_processor_interface.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
from __future__ import annotations
2+
3+
import asyncio
14
from abc import ABC, abstractmethod
25

36
from agentex.types.span import Span
47
from agentex.lib.types.tracing import TracingProcessorConfig
8+
from agentex.lib.utils.logging import make_logger
9+
10+
logger = make_logger(__name__)
511

612

713
class SyncTracingProcessor(ABC):
@@ -35,6 +41,43 @@ async def on_span_start(self, span: Span) -> None:
3541
async def on_span_end(self, span: Span) -> None:
3642
pass
3743

44+
async def on_spans_start(self, spans: list[Span]) -> None:
45+
"""Batched variant of on_span_start.
46+
47+
Default fallback fans out to the single-span method in parallel so
48+
existing processors keep working unchanged. Processors that support
49+
real batching (e.g. sending all spans in one HTTP call) should
50+
override this to avoid the per-span round trip.
51+
52+
Per-span exceptions are captured and logged individually so that one
53+
failing span does not prevent the others from being processed.
54+
"""
55+
results = await asyncio.gather(
56+
*(self.on_span_start(s) for s in spans), return_exceptions=True
57+
)
58+
for span, result in zip(spans, results):
59+
if isinstance(result, Exception):
60+
logger.error(
61+
"Tracing processor %s failed on_span_start for span %s",
62+
type(self).__name__,
63+
span.id,
64+
exc_info=result,
65+
)
66+
67+
async def on_spans_end(self, spans: list[Span]) -> None:
68+
"""Batched variant of on_span_end. See on_spans_start for details."""
69+
results = await asyncio.gather(
70+
*(self.on_span_end(s) for s in spans), return_exceptions=True
71+
)
72+
for span, result in zip(spans, results):
73+
if isinstance(result, Exception):
74+
logger.error(
75+
"Tracing processor %s failed on_span_end for span %s",
76+
type(self).__name__,
77+
span.id,
78+
exc_info=result,
79+
)
80+
3881
@abstractmethod
3982
async def shutdown(self) -> None:
4083
pass

src/agentex/lib/core/tracing/span_queue.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,29 +95,40 @@ async def _drain_loop(self) -> None:
9595

9696
@staticmethod
9797
async def _process_items(items: list[_SpanQueueItem]) -> None:
98-
"""Process a list of span events concurrently."""
98+
"""Dispatch a batch of same-event-type items to each processor in one call.
9999
100-
async def _handle(item: _SpanQueueItem) -> None:
100+
Groups spans by processor so each processor sees its full slice of the
101+
drain batch at once. Processors that override the batched methods can
102+
then send a single HTTP request per drain cycle instead of N.
103+
"""
104+
if not items:
105+
return
106+
107+
event_type = items[0].event_type
108+
assert all(i.event_type == event_type for i in items), (
109+
"_process_items requires all items to share the same event_type; "
110+
"callers must split START and END batches before dispatching."
111+
)
112+
by_processor: dict[AsyncTracingProcessor, list[Span]] = {}
113+
for item in items:
114+
for p in item.processors:
115+
by_processor.setdefault(p, []).append(item.span)
116+
117+
async def _handle(p: AsyncTracingProcessor, spans: list[Span]) -> None:
101118
try:
102-
if item.event_type == SpanEventType.START:
103-
coros = [p.on_span_start(item.span) for p in item.processors]
119+
if event_type == SpanEventType.START:
120+
await p.on_spans_start(spans)
104121
else:
105-
coros = [p.on_span_end(item.span) for p in item.processors]
106-
results = await asyncio.gather(*coros, return_exceptions=True)
107-
for result in results:
108-
if isinstance(result, Exception):
109-
logger.error(
110-
"Tracing processor error during %s for span %s",
111-
item.event_type.value,
112-
item.span.id,
113-
exc_info=result,
114-
)
122+
await p.on_spans_end(spans)
115123
except Exception:
116124
logger.exception(
117-
"Unexpected error in span queue for span %s", item.span.id
125+
"Tracing processor %s failed handling %d spans during %s",
126+
type(p).__name__,
127+
len(spans),
128+
event_type.value,
118129
)
119130

120-
await asyncio.gather(*[_handle(item) for item in items])
131+
await asyncio.gather(*[_handle(p, spans) for p, spans in by_processor.items()])
121132

122133
# ------------------------------------------------------------------
123134
# Shutdown

src/agentex/resources/tasks.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,27 @@ def list(
109109
order_direction: str | Omit = omit,
110110
page_number: int | Omit = omit,
111111
relationships: List[Literal["agents"]] | Omit = omit,
112+
status: Optional[Literal["CANCELED", "COMPLETED", "FAILED", "RUNNING", "TERMINATED", "TIMED_OUT", "DELETED"]]
113+
| Omit = omit,
114+
task_metadata: Optional[str] | Omit = omit,
112115
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
113116
# The extra values given here take precedence over values defined on the client or passed to this method.
114117
extra_headers: Headers | None = None,
115118
extra_query: Query | None = None,
116119
extra_body: Body | None = None,
117120
timeout: float | httpx.Timeout | None | NotGiven = not_given,
118121
) -> TaskListResponse:
119-
"""
120-
List all tasks.
122+
"""List all tasks.
121123
122124
Args:
125+
status: Filter tasks by status (e.g.
126+
127+
RUNNING, COMPLETED).
128+
129+
task_metadata:
130+
JSON-encoded object used to filter tasks via JSONB containment. Example:
131+
{"created_by_user_id": "abc-123"}.
132+
123133
extra_headers: Send extra headers
124134
125135
extra_query: Add additional query parameters to the request
@@ -144,6 +154,8 @@ def list(
144154
"order_direction": order_direction,
145155
"page_number": page_number,
146156
"relationships": relationships,
157+
"status": status,
158+
"task_metadata": task_metadata,
147159
},
148160
task_list_params.TaskListParams,
149161
),
@@ -679,17 +691,27 @@ async def list(
679691
order_direction: str | Omit = omit,
680692
page_number: int | Omit = omit,
681693
relationships: List[Literal["agents"]] | Omit = omit,
694+
status: Optional[Literal["CANCELED", "COMPLETED", "FAILED", "RUNNING", "TERMINATED", "TIMED_OUT", "DELETED"]]
695+
| Omit = omit,
696+
task_metadata: Optional[str] | Omit = omit,
682697
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
683698
# The extra values given here take precedence over values defined on the client or passed to this method.
684699
extra_headers: Headers | None = None,
685700
extra_query: Query | None = None,
686701
extra_body: Body | None = None,
687702
timeout: float | httpx.Timeout | None | NotGiven = not_given,
688703
) -> TaskListResponse:
689-
"""
690-
List all tasks.
704+
"""List all tasks.
691705
692706
Args:
707+
status: Filter tasks by status (e.g.
708+
709+
RUNNING, COMPLETED).
710+
711+
task_metadata:
712+
JSON-encoded object used to filter tasks via JSONB containment. Example:
713+
{"created_by_user_id": "abc-123"}.
714+
693715
extra_headers: Send extra headers
694716
695717
extra_query: Add additional query parameters to the request
@@ -714,6 +736,8 @@ async def list(
714736
"order_direction": order_direction,
715737
"page_number": page_number,
716738
"relationships": relationships,
739+
"status": status,
740+
"task_metadata": task_metadata,
717741
},
718742
task_list_params.TaskListParams,
719743
),

src/agentex/types/agent_rpc_by_name_params.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ class ParamsCreateTaskRequest(TypedDict, total=False):
3535
params: Optional[Dict[str, object]]
3636
"""The parameters for the task"""
3737

38+
task_metadata: Optional[Dict[str, object]]
39+
"""Caller-provided metadata to persist on the task row.
40+
41+
Only applied at task creation; ignored if a task with this name already exists.
42+
Forwarded to the agent inside the ACP payload for backward compatibility.
43+
"""
44+
3845

3946
class ParamsCancelTaskRequest(TypedDict, total=False):
4047
task_id: Optional[str]

0 commit comments

Comments
 (0)