Skip to content

Commit 82d8810

Browse files
authored
Merge pull request #4 from scaleapi/release-please--branches--main--changes--next
release: 0.1.0-alpha.2
2 parents e532cee + e6b96dd commit 82d8810

File tree

12 files changed

+1405
-913
lines changed

12 files changed

+1405
-913
lines changed

.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.1.0-alpha.1"
2+
".": "0.1.0-alpha.2"
33
}

CHANGELOG.md

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

3+
## 0.1.0-alpha.2 (2025-07-22)
4+
5+
Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/scaleapi/agentex-python/compare/v0.1.0-alpha.1...v0.1.0-alpha.2)
6+
37
## 0.1.0-alpha.1 (2025-07-22)
48

59
Full Changelog: [v0.0.1-alpha.1...v0.1.0-alpha.1](https://github.com/scaleapi/agentex-python/compare/v0.0.1-alpha.1...v0.1.0-alpha.1)

examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/run_worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def main():
2222
worker = AgentexWorker(
2323
task_queue=task_queue_name,
2424
)
25-
25+
2626
await worker.run(
2727
activities=get_all_activities(),
2828
workflow=At010AgentChatWorkflow,

examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/workflow.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ async def on_task_event_send(self, params: SendEventParams) -> None:
7070

7171
if not params.event.content:
7272
return
73-
7473
if params.event.content.type != "text":
7574
raise ValueError(f"Expected text message, got {params.event.content.type}")
7675

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agentex"
3-
version = "0.1.0-alpha.1"
3+
version = "0.1.0-alpha.2"
44
description = "The official Python library for the agentex API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -19,6 +19,7 @@ dependencies = [
1919
"rich>=13.9.2,<14",
2020
"fastapi>=0.115.0,<0.116",
2121
"uvicorn>=0.31.1",
22+
"watchfiles>=0.24.0,<1.0",
2223
"python-on-whales>=0.73.0,<0.74",
2324
"pyyaml>=6.0.2,<7",
2425
"jsonschema>=4.23.0,<5",

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.1.0-alpha.1" # x-release-please-version
4+
__version__ = "0.1.0-alpha.2" # x-release-please-version

src/agentex/lib/cli/commands/agents.py

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
build_agent,
1212
run_agent,
1313
)
14+
from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows
1415
from agentex.lib.cli.handlers.deploy_handlers import (
1516
DeploymentError,
1617
HelmError,
@@ -71,6 +72,35 @@ def delete(
7172
logger.info(f"Agent deleted: {agent_name}")
7273

7374

75+
@agents.command()
76+
def cleanup_workflows(
77+
agent_name: str = typer.Argument(..., help="Name of the agent to cleanup workflows for"),
78+
force: bool = typer.Option(False, help="Force cleanup using direct Temporal termination (bypasses development check)"),
79+
):
80+
"""
81+
Clean up all running workflows for an agent.
82+
83+
By default, uses graceful cancellation via agent RPC.
84+
With --force, directly terminates workflows via Temporal client.
85+
This is a convenience command that does the same thing as 'agentex tasks cleanup'.
86+
"""
87+
try:
88+
console.print(f"[blue]Cleaning up workflows for agent '{agent_name}'...[/blue]")
89+
90+
cleanup_agent_workflows(
91+
agent_name=agent_name,
92+
force=force,
93+
development_only=True
94+
)
95+
96+
console.print(f"[green]✓ Workflow cleanup completed for agent '{agent_name}'[/green]")
97+
98+
except Exception as e:
99+
console.print(f"[red]Cleanup failed: {str(e)}[/red]")
100+
logger.exception("Agent workflow cleanup failed")
101+
raise typer.Exit(1) from e
102+
103+
74104
@agents.command()
75105
def build(
76106
manifest: str = typer.Option(..., help="Path to the manifest you want to use"),
@@ -80,6 +110,9 @@ def build(
80110
repository_name: str | None = typer.Option(
81111
None, help="Repository name to use for the built image"
82112
),
113+
platforms: str | None = typer.Option(
114+
None, help="Platform to build the image for. Please enter a comma separated list of platforms."
115+
),
83116
push: bool = typer.Option(False, help="Whether to push the image to the registry"),
84117
secret: str | None = typer.Option(
85118
None,
@@ -98,20 +131,33 @@ def build(
98131
"""
99132
typer.echo(f"Building agent image from manifest: {manifest}")
100133

134+
# Validate required parameters for building
135+
if push and not registry:
136+
typer.echo("Error: --registry is required when --push is enabled", err=True)
137+
raise typer.Exit(1)
138+
139+
# Only proceed with build if we have a registry (for now, to match existing behavior)
140+
if not registry:
141+
typer.echo("No registry provided, skipping image build")
142+
return
143+
144+
platform_list = platforms.split(",") if platforms else []
145+
101146
try:
102147
image_url = build_agent(
103148
manifest_path=manifest,
104-
registry_url=registry,
105-
repository_name=repository_name,
149+
registry_url=registry, # Now guaranteed to be non-None
150+
repository_name=repository_name or "default-repo", # Provide default
151+
platforms=platform_list,
106152
push=push,
107-
secret=secret,
108-
tag=tag,
109-
build_args=build_arg,
153+
secret=secret or "", # Provide default empty string
154+
tag=tag or "latest", # Provide default
155+
build_args=build_arg or [], # Provide default empty list
110156
)
111157
if image_url:
112158
typer.echo(f"Successfully built image: {image_url}")
113159
else:
114-
typer.echo("No registry provided, image was not built")
160+
typer.echo("Image build completed but no URL returned")
115161
except Exception as e:
116162
typer.echo(f"Error building agent image: {str(e)}", err=True)
117163
logger.exception("Error building agent image")
@@ -121,11 +167,35 @@ def build(
121167
@agents.command()
122168
def run(
123169
manifest: str = typer.Option(..., help="Path to the manifest you want to use"),
170+
cleanup_on_start: bool = typer.Option(
171+
False,
172+
help="Clean up existing workflows for this agent before starting"
173+
),
124174
):
125175
"""
126176
Run an agent locally from the given manifest.
127177
"""
128178
typer.echo(f"Running agent from manifest: {manifest}")
179+
180+
# Optionally cleanup existing workflows before starting
181+
if cleanup_on_start:
182+
try:
183+
# Parse manifest to get agent name
184+
manifest_obj = AgentManifest.from_yaml(file_path=manifest)
185+
agent_name = manifest_obj.agent.name
186+
187+
console.print(f"[yellow]Cleaning up existing workflows for agent '{agent_name}'...[/yellow]")
188+
cleanup_agent_workflows(
189+
agent_name=agent_name,
190+
force=False,
191+
development_only=True
192+
)
193+
console.print("[green]✓ Pre-run cleanup completed[/green]")
194+
195+
except Exception as e:
196+
console.print(f"[yellow]⚠ Pre-run cleanup failed: {str(e)}[/yellow]")
197+
logger.warning(f"Pre-run cleanup failed: {e}")
198+
129199
try:
130200
run_agent(manifest_path=manifest)
131201
except Exception as e:

src/agentex/lib/cli/commands/tasks.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import typer
22
from rich import print_json
3+
from rich.console import Console
34

45
from agentex import Agentex
6+
from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows
57
from agentex.lib.utils.logging import make_logger
68

79
logger = make_logger(__name__)
10+
console = Console()
811

912
tasks = typer.Typer()
1013

@@ -33,6 +36,47 @@ def list():
3336
print_json(data=[task.to_dict() for task in tasks])
3437

3538

39+
@tasks.command()
40+
def list_running(
41+
agent_name: str = typer.Option(..., help="Name of the agent to list running tasks for"),
42+
):
43+
"""
44+
List all currently running tasks for a specific agent.
45+
"""
46+
client = Agentex()
47+
all_tasks = client.tasks.list()
48+
running_tasks = [task for task in all_tasks if hasattr(task, 'status') and task.status == "RUNNING"]
49+
50+
if not running_tasks:
51+
console.print(f"[yellow]No running tasks found for agent '{agent_name}'[/yellow]")
52+
return
53+
54+
console.print(f"[green]Found {len(running_tasks)} running task(s) for agent '{agent_name}':[/green]")
55+
56+
# Convert to dict with proper datetime serialization
57+
serializable_tasks = []
58+
for task in running_tasks:
59+
try:
60+
# Use model_dump with mode='json' for proper datetime handling
61+
if hasattr(task, 'model_dump'):
62+
serializable_tasks.append(task.model_dump(mode='json'))
63+
else:
64+
# Fallback for non-Pydantic objects
65+
serializable_tasks.append({
66+
"id": getattr(task, 'id', 'unknown'),
67+
"status": getattr(task, 'status', 'unknown')
68+
})
69+
except Exception as e:
70+
logger.warning(f"Failed to serialize task: {e}")
71+
# Minimal fallback
72+
serializable_tasks.append({
73+
"id": getattr(task, 'id', 'unknown'),
74+
"status": getattr(task, 'status', 'unknown')
75+
})
76+
77+
print_json(data=serializable_tasks)
78+
79+
3680
@tasks.command()
3781
def delete(
3882
task_id: str = typer.Argument(..., help="ID of the task to delete"),
@@ -44,3 +88,31 @@ def delete(
4488
client = Agentex()
4589
client.tasks.delete(task_id=task_id)
4690
logger.info(f"Task deleted: {task_id}")
91+
92+
93+
@tasks.command()
94+
def cleanup(
95+
agent_name: str = typer.Option(..., help="Name of the agent to cleanup tasks for"),
96+
force: bool = typer.Option(False, help="Force cleanup using direct Temporal termination (bypasses development check)"),
97+
):
98+
"""
99+
Clean up all running tasks/workflows for an agent.
100+
101+
By default, uses graceful cancellation via agent RPC.
102+
With --force, directly terminates workflows via Temporal client.
103+
"""
104+
try:
105+
console.print(f"[blue]Starting cleanup for agent '{agent_name}'...[/blue]")
106+
107+
cleanup_agent_workflows(
108+
agent_name=agent_name,
109+
force=force,
110+
development_only=True
111+
)
112+
113+
console.print(f"[green]✓ Cleanup completed for agent '{agent_name}'[/green]")
114+
115+
except Exception as e:
116+
console.print(f"[red]Cleanup failed: {str(e)}[/red]")
117+
logger.exception("Task cleanup failed")
118+
raise typer.Exit(1) from e

src/agentex/lib/cli/handlers/agent_handlers.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def build_agent(
2222
manifest_path: str,
2323
registry_url: str,
2424
repository_name: str,
25+
platforms: list[str],
2526
push: bool = False,
2627
secret: str = None,
2728
tag: str = None,
@@ -73,7 +74,7 @@ def build_agent(
7374
"context_path": str(build_context.path),
7475
"file": str(build_context.path / build_context.dockerfile_path),
7576
"tags": [image_name],
76-
"platforms": ["linux/amd64"],
77+
"platforms": platforms,
7778
}
7879

7980
# Add Docker build args if provided
@@ -128,8 +129,32 @@ def build_agent(
128129
def run_agent(manifest_path: str):
129130
"""Run an agent locally from the given manifest"""
130131
import asyncio
131-
132+
import signal
133+
import sys
134+
135+
# Flag to track if we're shutting down
136+
shutting_down = False
137+
138+
def signal_handler(signum, frame):
139+
"""Handle signals by raising KeyboardInterrupt"""
140+
nonlocal shutting_down
141+
if shutting_down:
142+
# If we're already shutting down and get another signal, force exit
143+
print(f"\nForce exit on signal {signum}")
144+
sys.exit(1)
145+
146+
shutting_down = True
147+
print(f"\nReceived signal {signum}, shutting down...")
148+
raise KeyboardInterrupt()
149+
150+
# Set up signal handling for the main thread
151+
signal.signal(signal.SIGINT, signal_handler)
152+
signal.signal(signal.SIGTERM, signal_handler)
153+
132154
try:
133155
asyncio.run(_run_agent(manifest_path))
156+
except KeyboardInterrupt:
157+
print("Shutdown completed.")
158+
sys.exit(0)
134159
except RunError as e:
135160
raise RuntimeError(str(e)) from e

0 commit comments

Comments
 (0)