Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a9cacf4
feat(harness): unified harness surface — foundation (span derivation,…
declan-scale Jun 22, 2026
c8de1d4
feat(streaming): stream tool call argument deltas in TemporalStreamin…
vkalmathscale Jun 22, 2026
694960f
fix(harness): assert cross-channel (yield vs auto-send) conformance e…
declan-scale Jun 22, 2026
5ec62c2
feat(pydantic-ai): migrate onto unified harness surface (PR4) (#415)
declan-scale Jun 22, 2026
d10e151
feat(openai-agents): migrate onto the unified harness surface (#416)
declan-scale Jun 22, 2026
904339c
feat(claude-code): stream-json parser tap for the unified harness sur…
declan-scale Jun 22, 2026
d344228
feat(langgraph): migrate LangGraph harness onto unified surface (#417)
declan-scale Jun 22, 2026
9b2b031
feat(codex): event-stream parser tap for the unified harness surface …
declan-scale Jun 22, 2026
fa60632
feat(harness): public adk facade + docs for the unified harness surfa…
declan-scale Jun 23, 2026
2d63eef
fix(tracing): fail open temporal span activities (#437)
danielmillerp Jun 23, 2026
10d22a2
feat(tracing): skip Agentex span-start write by default (end-only ing…
NiteshDhanpal Jun 23, 2026
7f6d70a
test(harness): shared test fakes + conformance determinism fix (#427)
declan-scale Jun 23, 2026
c5c0a3a
feat(api): api update
stainless-app[bot] Jun 23, 2026
ee491f3
Merge remote-tracking branch 'origin/production--c5c0a3ab1cd2dd26ea58…
declan-scale Jul 9, 2026
f1c1252
feat(api): add webhook endpoint
stainless-app[bot] Jul 9, 2026
0793543
chore(internal): version bump
stainless-app[bot] Jun 24, 2026
8f084b6
feat(api): remove retrieve/delete/pause/trigger/unpause, update creat…
stainless-app[bot] Jun 29, 2026
fcddeea
chore(internal): version bump
stainless-app[bot] Jun 29, 2026
7aeb893
chore(internal): version bump
stainless-app[bot] Jul 1, 2026
9145865
feat: Use stable handles for run schedules
stainless-app[bot] Jul 9, 2026
c1e7db8
feat(api): update schedule configs
stainless-app[bot] Jul 9, 2026
56f41aa
feat(api): add schedule resume
stainless-app[bot] Jul 9, 2026
e3c8baf
feat(api): manual updates
stainless-app[bot] Jul 9, 2026
37f89ca
codegen metadata
stainless-app[bot] Jul 9, 2026
619c9c2
codegen metadata
stainless-app[bot] Jul 9, 2026
14c124d
fix: cap openai <2.45 for openai-agents 0.14.x compatibility (#459)
declan-scale Jul 10, 2026
de49d43
feat(api): add skipped_action_times field to agents schedule responses
stainless-app[bot] Jul 10, 2026
76af59f
fix(tracing): capture span body exceptions and export SGP status=ERROR
NiteshDhanpal Jul 10, 2026
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
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 65
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-cd43ba4b554ca024dd7ee7b74e4f4700a743282c17def704a0967e6ff251c09b.yml
openapi_spec_hash: 9369ccc9c0289e9d6f641a526d244d1c
config_hash: 1ae003838971335aac550f3ad5872f54
configured_endpoints: 72
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-ad493b43ecb1652a9e522451c616e33775e628c1d79c224dff34855bd82c8c7c.yml
openapi_spec_hash: 77044dc5774eb88f1541ebbe53b413a7
config_hash: 50803c6c42bb738099163f3f4d3cdeed
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ

Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.

## Nested params

Nested parameters are dictionaries, typed using `TypedDict`, for example:

```python
from agentex import Agentex

client = Agentex()

schedule = client.agents.schedules.create(
agent_id="agent_id",
initial_input={"content": "content"},
name="name",
)
print(schedule.initial_input)
```

## Handling errors

When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `agentex.APIConnectionError` is raised.
Expand Down
9 changes: 8 additions & 1 deletion adk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ dependencies = [
# LLM provider integrations
"litellm>=1.83.7,<2",
"openai-agents>=0.14.3,<0.15",
"openai>=2.2,<3", # Required by openai-agents; litellm now supports openai 2.x (issue #13711 resolved: https://github.com/BerriAI/litellm/issues/13711)
# Cap <2.45: openai 2.45.0 makes InputTokensDetails.cache_write_tokens a
# required field, but openai-agents 0.14.x still builds
# InputTokensDetails(cached_tokens=0) (agents/usage.py), so every
# Runner.run_streamed raises a pydantic ValidationError at context setup.
# openai-agents 0.14.8 is the latest release, so there is no newer version
# to bump to; drop this ceiling once openai-agents ships a fix.
# litellm now supports openai 2.x (issue #13711 resolved: https://github.com/BerriAI/litellm/issues/13711)
"openai>=2.2,<2.45",
"claude-agent-sdk>=0.1.0",
"pydantic-ai-slim>=1.0,<2",
"langgraph-checkpoint>=2.0.0",
Expand Down
25 changes: 19 additions & 6 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,35 @@ Types:
from agentex.types.agents import (
ScheduleCreateResponse,
ScheduleRetrieveResponse,
ScheduleUpdateResponse,
ScheduleListResponse,
SchedulePauseResponse,
SchedulePauseByNameResponse,
ScheduleResumeResponse,
ScheduleResumeByNameResponse,
ScheduleRetrieveByNameResponse,
ScheduleTriggerResponse,
ScheduleUnpauseResponse,
ScheduleTriggerByNameResponse,
ScheduleUpdateByNameResponse,
)
```

Methods:

- <code title="post /agents/{agent_id}/schedules">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">create</a>(agent_id, \*\*<a href="src/agentex/types/agents/schedule_create_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_create_response.py">ScheduleCreateResponse</a></code>
- <code title="get /agents/{agent_id}/schedules/{schedule_name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">retrieve</a>(schedule_name, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_retrieve_response.py">ScheduleRetrieveResponse</a></code>
- <code title="get /agents/{agent_id}/schedules/{schedule_id}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">retrieve</a>(schedule_id, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_retrieve_response.py">ScheduleRetrieveResponse</a></code>
- <code title="patch /agents/{agent_id}/schedules/{schedule_id}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">update</a>(schedule_id, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_update_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_update_response.py">ScheduleUpdateResponse</a></code>
- <code title="get /agents/{agent_id}/schedules">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">list</a>(agent_id, \*\*<a href="src/agentex/types/agents/schedule_list_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_list_response.py">ScheduleListResponse</a></code>
- <code title="delete /agents/{agent_id}/schedules/{schedule_name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">delete</a>(schedule_name, \*, agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_name}/pause">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">pause</a>(schedule_name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_pause_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_pause_response.py">SchedulePauseResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_name}/trigger">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">trigger</a>(schedule_name, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_trigger_response.py">ScheduleTriggerResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_name}/unpause">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">unpause</a>(schedule_name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_unpause_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_unpause_response.py">ScheduleUnpauseResponse</a></code>
- <code title="delete /agents/{agent_id}/schedules/{schedule_id}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">delete</a>(schedule_id, \*, agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="delete /agents/{agent_id}/schedules/name/{name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">delete_by_name</a>(name, \*, agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_id}/pause">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">pause</a>(schedule_id, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_pause_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_pause_response.py">SchedulePauseResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/name/{name}/pause">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">pause_by_name</a>(name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_pause_by_name_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_pause_by_name_response.py">SchedulePauseByNameResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_id}/resume">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">resume</a>(schedule_id, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_resume_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_resume_response.py">ScheduleResumeResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/name/{name}/resume">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">resume_by_name</a>(name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_resume_by_name_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_resume_by_name_response.py">ScheduleResumeByNameResponse</a></code>
- <code title="get /agents/{agent_id}/schedules/name/{name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">retrieve_by_name</a>(name, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_retrieve_by_name_response.py">ScheduleRetrieveByNameResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_id}/trigger">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">trigger</a>(schedule_id, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_trigger_response.py">ScheduleTriggerResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/name/{name}/trigger">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">trigger_by_name</a>(name, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_trigger_by_name_response.py">ScheduleTriggerByNameResponse</a></code>
- <code title="patch /agents/{agent_id}/schedules/name/{name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">update_by_name</a>(path_name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_update_by_name_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_update_by_name_response.py">ScheduleUpdateByNameResponse</a></code>

# Tasks

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from agentex.lib.utils.logging import make_logger
from agentex.lib.core.observability import tracing_metrics_recording as _metrics
from agentex.lib.environment_variables import EnvironmentVariables
from agentex.lib.core.tracing.span_error import get_span_error
from agentex.lib.core.tracing.processors.tracing_processor_interface import (
SyncTracingProcessor,
AsyncTracingProcessor,
Expand Down Expand Up @@ -83,6 +84,9 @@ def _build_sgp_span(span: Span, env_vars: EnvironmentVariables) -> SGPSpan:
),
)
sgp_span.start_time = span.start_time.isoformat() # type: ignore[union-attr]
error = get_span_error(span)
if error is not None:
sgp_span.set_error(error_type=error["type"], error_message=error["message"])
return sgp_span


Expand Down
36 changes: 36 additions & 0 deletions src/agentex/lib/core/tracing/span_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

from typing import Any

from agentex.types.span import Span

# Reserved key under ``Span.data`` carrying failure info for a span whose
# context-manager body raised. Mirrors the existing ``__span_type__`` /
# ``__source__`` reserved-key convention already read/written by the SGP
# processor. Stored in ``data`` because the Span model is generated from the
# OpenAPI spec and has no first-class status/error field; ``data`` is a real
# field, so it survives ``model_copy(deep=True)`` and round-trips to both the
# SGP and agentex-native span stores.
SPAN_ERROR_KEY = "__error__"


def set_span_error(span: Span, exc: BaseException) -> None:
"""Record an exception on ``span`` under ``data[SPAN_ERROR_KEY]``.

No-op when ``span.data`` is a list (matching ``_add_source_to_span``, which
only attaches metadata to dict-shaped data).
"""
error = {"type": type(exc).__name__, "message": str(exc)}
if span.data is None:
span.data = {}
if isinstance(span.data, dict):
span.data[SPAN_ERROR_KEY] = error


def get_span_error(span: Span) -> dict[str, Any] | None:
"""Return the error recorded by :func:`set_span_error`, or ``None``."""
if isinstance(span.data, dict):
value = span.data.get(SPAN_ERROR_KEY)
if isinstance(value, dict):
return value
return None
7 changes: 7 additions & 0 deletions src/agentex/lib/core/tracing/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.span_error import set_span_error
from agentex.lib.core.tracing.span_queue import (
SpanEventType,
AsyncSpanQueue,
Expand Down Expand Up @@ -165,6 +166,9 @@ def span(
span = self.start_span(name, parent_id, input, data, task_id=task_id)
try:
yield span
except Exception as exc:
set_span_error(span, exc)
raise
finally:
self.end_span(span)

Expand Down Expand Up @@ -321,5 +325,8 @@ async def span(
span = await self.start_span(name, parent_id, input, data, task_id=task_id)
try:
yield span
except Exception as exc:
set_span_error(span, exc)
raise
finally:
await self.end_span(span)
Loading
Loading