diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 31d823888..b8af36c3a 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.28.0"
+ ".": "1.29.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 63705699e..02013546c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 120
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-c1b0e0d2c2bf9d7888ddef0cfdfb390458875cd2c1feb48a06fcd06e92105326.yml
-openapi_spec_hash: ec61cf6d205251b54e1a6d68d9dd9860
-config_hash: 6d102da8b9fcc3d0deb42d7a707e78c6
+configured_endpoints: 121
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-a9327279b1ba17f0983d92a3065b04a2bc1b5e5e830cf099aef4f79072fe4075.yml
+openapi_spec_hash: b63815af36cf18c8cc70be416f9c0f71
+config_hash: 2b0535779b180339d10766ba74a96700
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 492ac3a8a..2598e218a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## 1.29.0 (2026-08-10)
+
+Full Changelog: [v1.28.0...v1.29.0](https://github.com/runloopai/api-client-python/compare/v1.28.0...v1.29.0)
+
+### Features
+
+* **axon:** add registry delete endpoint ([#10769](https://github.com/runloopai/api-client-python/issues/10769)) ([c63f011](https://github.com/runloopai/api-client-python/commit/c63f01108d98785b358357e4e65e5e517dd0805e))
+* **broker:** plumb Pi protocol selection ([#10256](https://github.com/runloopai/api-client-python/issues/10256)) ([974d52b](https://github.com/runloopai/api-client-python/commit/974d52b8743ceda8a7127e3a3432ce51e71b1bfd))
+* **converter:** add Helm chart, container image, and enable in dev ([#10405](https://github.com/runloopai/api-client-python/issues/10405)) ([0d5ca34](https://github.com/runloopai/api-client-python/commit/0d5ca3438ac4cbcafdfaf7584042bd78a956e3ae))
+* **devbox:** recover resume provisioning failures ([#10597](https://github.com/runloopai/api-client-python/issues/10597)) ([518301e](https://github.com/runloopai/api-client-python/commit/518301e444b95bc5fa73cc0f13be67cf3f750024))
+
## 1.28.0 (2026-07-29)
Full Changelog: [v1.27.0...v1.28.0](https://github.com/runloopai/api-client-python/compare/v1.27.0...v1.28.0)
diff --git a/api.md b/api.md
index 6becb60c5..584c2d2f8 100644
--- a/api.md
+++ b/api.md
@@ -131,6 +131,7 @@ Methods:
- client.axons.create(\*\*params) -> AxonView
- client.axons.retrieve(id) -> AxonView
- client.axons.list(\*\*params) -> SyncAxonsCursorIDPage[AxonView]
+- client.axons.delete(id) -> object
- client.axons.publish(id, \*\*params) -> PublishResultView
- client.axons.subscribe_sse(id, \*\*params) -> AxonEventView
diff --git a/pyproject.toml b/pyproject.toml
index 364c6dedd..7974ff247 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "runloop_api_client"
-version = "1.28.0"
+version = "1.29.0"
description = "The official Python library for the runloop API"
dynamic = ["readme"]
license = "MIT"
diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py
index 1a2d135f9..cb57bc803 100644
--- a/src/runloop_api_client/_version.py
+++ b/src/runloop_api_client/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "runloop_api_client"
-__version__ = "1.28.0" # x-release-please-version
+__version__ = "1.29.0" # x-release-please-version
diff --git a/src/runloop_api_client/resources/axons/axons.py b/src/runloop_api_client/resources/axons/axons.py
index fba62a339..232f6f4c2 100644
--- a/src/runloop_api_client/resources/axons/axons.py
+++ b/src/runloop_api_client/resources/axons/axons.py
@@ -210,6 +210,46 @@ def list(
model=AxonView,
)
+ def delete(
+ self,
+ id: str,
+ *,
+ # 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> object:
+ """
+ [Beta] Mark an axon deleted.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._delete(
+ path_template("/v1/axons/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=object,
+ )
+
def publish(
self,
id: str,
@@ -485,6 +525,46 @@ def list(
model=AxonView,
)
+ async def delete(
+ self,
+ id: str,
+ *,
+ # 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> object:
+ """
+ [Beta] Mark an axon deleted.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._delete(
+ path_template("/v1/axons/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=object,
+ )
+
async def publish(
self,
id: str,
@@ -607,6 +687,9 @@ def __init__(self, axons: AxonsResource) -> None:
self.list = to_raw_response_wrapper(
axons.list,
)
+ self.delete = to_raw_response_wrapper(
+ axons.delete,
+ )
self.publish = to_raw_response_wrapper(
axons.publish,
)
@@ -636,6 +719,9 @@ def __init__(self, axons: AsyncAxonsResource) -> None:
self.list = async_to_raw_response_wrapper(
axons.list,
)
+ self.delete = async_to_raw_response_wrapper(
+ axons.delete,
+ )
self.publish = async_to_raw_response_wrapper(
axons.publish,
)
@@ -665,6 +751,9 @@ def __init__(self, axons: AxonsResource) -> None:
self.list = to_streamed_response_wrapper(
axons.list,
)
+ self.delete = to_streamed_response_wrapper(
+ axons.delete,
+ )
self.publish = to_streamed_response_wrapper(
axons.publish,
)
@@ -694,6 +783,9 @@ def __init__(self, axons: AsyncAxonsResource) -> None:
self.list = async_to_streamed_response_wrapper(
axons.list,
)
+ self.delete = async_to_streamed_response_wrapper(
+ axons.delete,
+ )
self.publish = async_to_streamed_response_wrapper(
axons.publish,
)
diff --git a/src/runloop_api_client/types/blueprint_view.py b/src/runloop_api_client/types/blueprint_view.py
index 87c1eecab..b0bfefecd 100644
--- a/src/runloop_api_client/types/blueprint_view.py
+++ b/src/runloop_api_client/types/blueprint_view.py
@@ -63,7 +63,7 @@ class BlueprintView(BaseModel):
state: Literal["created", "deleted"]
"""The state of the Blueprint."""
- status: Literal["queued", "provisioning", "building", "failed", "build_complete"]
+ status: Literal["queued", "provisioning", "building", "awaiting_upload", "failed", "build_complete"]
"""The status of the Blueprint build."""
base_blueprint_id: Optional[str] = None
diff --git a/src/runloop_api_client/types/devbox_view.py b/src/runloop_api_client/types/devbox_view.py
index 49ffc1ec8..504d1fe2e 100644
--- a/src/runloop_api_client/types/devbox_view.py
+++ b/src/runloop_api_client/types/devbox_view.py
@@ -11,6 +11,9 @@
class StateTransition(BaseModel):
+ failure_reason: Optional[str] = None
+ """The failure that caused this state transition, if applicable."""
+
status: Optional[
Literal[
"scheduled",
diff --git a/src/runloop_api_client/types/shared/broker_mount.py b/src/runloop_api_client/types/shared/broker_mount.py
index adf49c04a..97e6c940a 100644
--- a/src/runloop_api_client/types/shared/broker_mount.py
+++ b/src/runloop_api_client/types/shared/broker_mount.py
@@ -17,16 +17,18 @@ class BrokerMount(BaseModel):
agent_binary: Optional[str] = None
"""Binary to launch the agent (e.g., 'opencode').
- Used by protocols that launch a subprocess (acp, claude_json, codex_json).
+ Used by protocols that launch a subprocess (acp, claude_json, codex_json,
+ pi_json).
"""
launch_args: Optional[List[str]] = None
"""Arguments to pass to the agent command (e.g., ['acp']).
- Used by protocols that launch a subprocess (acp, claude_json, codex_json).
+ Used by protocols that launch a subprocess (acp, claude_json, codex_json,
+ pi_json).
"""
- protocol: Optional[Literal["acp", "claude_json", "codex_json"]] = None
+ protocol: Optional[Literal["acp", "claude_json", "codex_json", "pi_json"]] = None
"""The protocol used by the broker to deliver events to the agent."""
working_directory: Optional[str] = None
diff --git a/src/runloop_api_client/types/shared_params/broker_mount.py b/src/runloop_api_client/types/shared_params/broker_mount.py
index e62bd59e5..55e6b6661 100644
--- a/src/runloop_api_client/types/shared_params/broker_mount.py
+++ b/src/runloop_api_client/types/shared_params/broker_mount.py
@@ -19,16 +19,18 @@ class BrokerMount(TypedDict, total=False):
agent_binary: Optional[str]
"""Binary to launch the agent (e.g., 'opencode').
- Used by protocols that launch a subprocess (acp, claude_json, codex_json).
+ Used by protocols that launch a subprocess (acp, claude_json, codex_json,
+ pi_json).
"""
launch_args: Optional[SequenceNotStr[str]]
"""Arguments to pass to the agent command (e.g., ['acp']).
- Used by protocols that launch a subprocess (acp, claude_json, codex_json).
+ Used by protocols that launch a subprocess (acp, claude_json, codex_json,
+ pi_json).
"""
- protocol: Optional[Literal["acp", "claude_json", "codex_json"]]
+ protocol: Optional[Literal["acp", "claude_json", "codex_json", "pi_json"]]
"""The protocol used by the broker to deliver events to the agent."""
working_directory: Optional[str]
diff --git a/tests/api_resources/test_axons.py b/tests/api_resources/test_axons.py
index 8764443bb..97f4f663c 100644
--- a/tests/api_resources/test_axons.py
+++ b/tests/api_resources/test_axons.py
@@ -128,6 +128,44 @@ def test_streaming_response_list(self, client: Runloop) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_method_delete(self, client: Runloop) -> None:
+ axon = client.axons.delete(
+ "id",
+ )
+ assert_matches_type(object, axon, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Runloop) -> None:
+ response = client.axons.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ axon = response.parse()
+ assert_matches_type(object, axon, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Runloop) -> None:
+ with client.axons.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ axon = response.parse()
+ assert_matches_type(object, axon, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.axons.with_raw_response.delete(
+ "",
+ )
+
@parametrize
def test_method_publish(self, client: Runloop) -> None:
axon = client.axons.publish(
@@ -340,6 +378,44 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None
assert cast(Any, response.is_closed) is True
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncRunloop) -> None:
+ axon = await async_client.axons.delete(
+ "id",
+ )
+ assert_matches_type(object, axon, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.axons.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ axon = await response.parse()
+ assert_matches_type(object, axon, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncRunloop) -> None:
+ async with async_client.axons.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ axon = await response.parse()
+ assert_matches_type(object, axon, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.axons.with_raw_response.delete(
+ "",
+ )
+
@parametrize
async def test_method_publish(self, async_client: AsyncRunloop) -> None:
axon = await async_client.axons.publish(