Skip to content

Commit 72ab207

Browse files
SWE Destroyerclaude
andcommitted
Centralize port constants and make ports configurable
Replace hardcoded port numbers throughout the codebase with centralized constants from agentex.lib.constants.ports. This eliminates scattered magic numbers and makes all default ports easy to discover and change. Port changes: - Health check: 8080 -> 8719 (avoids common proxy port) - Debug worker: 5678 -> 5688 (avoids debugpy default) - Debug ACP: 5679 -> 5689 (debug worker + 1) - Redis: 6379 -> 6389 (avoids standard Redis port) - ACP/API/Temporal ports unchanged (already non-standard) All ports remain overridable via their respective environment variables (ACP_PORT, HEALTH_CHECK_PORT, AGENTEX_DEBUG_PORT, REDIS_URL, etc). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c7162a6 commit 72ab207

File tree

17 files changed

+247
-216
lines changed

17 files changed

+247
-216
lines changed

src/agentex/_client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@
5353
"AsyncClient",
5454
]
5555

56+
from agentex.lib.constants.ports import AGENTEX_API_BASE_URL
57+
5658
ENVIRONMENTS: Dict[str, str] = {
57-
"production": "http://localhost:5003",
58-
"development": "http://localhost:5003",
59+
"production": AGENTEX_API_BASE_URL,
60+
"development": AGENTEX_API_BASE_URL,
5961
}
6062

6163

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from agentex import Agentex
1313
from agentex.lib.cli.debug import DebugMode, DebugConfig
14+
from agentex.lib.constants.ports import DEBUG_PORT
1415
from agentex.lib.utils.logging import make_logger
1516
from agentex.lib.cli.utils.cli_utils import handle_questionary_cancellation
1617
from agentex.lib.sdk.config.validation import (
@@ -269,7 +270,7 @@ def run(
269270
debug: bool = typer.Option(False, help="Enable debug mode for both worker and ACP (disables auto-reload)"),
270271
debug_worker: bool = typer.Option(False, help="Enable debug mode for temporal worker only"),
271272
debug_acp: bool = typer.Option(False, help="Enable debug mode for ACP server only"),
272-
debug_port: int = typer.Option(5678, help="Port for remote debugging (worker uses this, ACP uses port+1)"),
273+
debug_port: int = typer.Option(DEBUG_PORT, help="Port for remote debugging (worker uses this, ACP uses port+1)"),
273274
wait_for_debugger: bool = typer.Option(False, help="Wait for debugger to attach before starting"),
274275
) -> None:
275276
"""

src/agentex/lib/cli/debug/debug_config.py

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import socket
66
from enum import Enum
77

8+
from agentex.lib.constants.ports import DEBUG_ACP_PORT, DEBUG_PORT
89
from agentex.lib.utils.model_utils import BaseModel
910

1011

1112
class DebugMode(str, Enum):
1213
"""Debug mode options"""
14+
1315
WORKER = "worker"
1416
ACP = "acp"
1517
BOTH = "both"
@@ -18,91 +20,73 @@ class DebugMode(str, Enum):
1820

1921
class DebugConfig(BaseModel):
2022
"""Configuration for debug mode"""
21-
23+
2224
enabled: bool = False
2325
mode: DebugMode = DebugMode.NONE
24-
port: int = 5678
26+
port: int = DEBUG_PORT
2527
wait_for_attach: bool = False
2628
auto_port: bool = True # Automatically find available port if specified port is busy
27-
29+
2830
@classmethod
2931
def create_worker_debug(
30-
cls,
31-
port: int = 5678,
32-
wait_for_attach: bool = False,
33-
auto_port: bool = True
32+
cls, port: int = DEBUG_PORT, wait_for_attach: bool = False, auto_port: bool = True
3433
) -> "DebugConfig":
3534
"""Create debug config for worker debugging"""
36-
return cls(
37-
enabled=True,
38-
mode=DebugMode.WORKER,
39-
port=port,
40-
wait_for_attach=wait_for_attach,
41-
auto_port=auto_port
42-
)
43-
35+
return cls(enabled=True, mode=DebugMode.WORKER, port=port, wait_for_attach=wait_for_attach, auto_port=auto_port)
36+
4437
@classmethod
4538
def create_acp_debug(
46-
cls,
47-
port: int = 5679,
48-
wait_for_attach: bool = False,
49-
auto_port: bool = True
39+
cls, port: int = DEBUG_ACP_PORT, wait_for_attach: bool = False, auto_port: bool = True
5040
) -> "DebugConfig":
5141
"""Create debug config for ACP debugging"""
52-
return cls(
53-
enabled=True,
54-
mode=DebugMode.ACP,
55-
port=port,
56-
wait_for_attach=wait_for_attach,
57-
auto_port=auto_port
58-
)
59-
42+
return cls(enabled=True, mode=DebugMode.ACP, port=port, wait_for_attach=wait_for_attach, auto_port=auto_port)
43+
6044
@classmethod
6145
def create_both_debug(
62-
cls,
63-
worker_port: int = 5678,
64-
_acp_port: int = 5679,
46+
cls,
47+
worker_port: int = DEBUG_PORT,
48+
_acp_port: int = DEBUG_ACP_PORT,
6549
wait_for_attach: bool = False,
66-
auto_port: bool = True
50+
auto_port: bool = True,
6751
) -> "DebugConfig":
6852
"""Create debug config for both worker and ACP debugging"""
6953
return cls(
7054
enabled=True,
7155
mode=DebugMode.BOTH,
7256
port=worker_port, # Primary port for worker
7357
wait_for_attach=wait_for_attach,
74-
auto_port=auto_port
58+
auto_port=auto_port,
7559
)
76-
60+
7761
def should_debug_worker(self) -> bool:
7862
"""Check if worker should be debugged"""
7963
return self.enabled and self.mode in (DebugMode.WORKER, DebugMode.BOTH)
80-
64+
8165
def should_debug_acp(self) -> bool:
8266
"""Check if ACP should be debugged"""
8367
return self.enabled and self.mode in (DebugMode.ACP, DebugMode.BOTH)
84-
68+
8569
def get_worker_port(self) -> int:
8670
"""Get port for worker debugging"""
8771
return self.port
88-
72+
8973
def get_acp_port(self) -> int:
9074
"""Get port for ACP debugging"""
9175
if self.mode == DebugMode.BOTH:
9276
return self.port + 1 # Use port + 1 for ACP when debugging both
9377
return self.port
9478

9579

96-
def find_available_port(start_port: int = 5678, max_attempts: int = 10) -> int:
80+
def find_available_port(start_port: int = DEBUG_PORT, max_attempts: int = 10) -> int:
9781
"""Find an available port starting from start_port"""
9882
for port in range(start_port, start_port + max_attempts):
9983
try:
10084
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
101-
s.bind(('localhost', port))
85+
s.bind(("localhost", port))
10286
return port
10387
except OSError:
10488
continue
105-
89+
10690
# If we can't find an available port, just return the start port
10791
# and let the debug server handle the error
10892
return start_port
@@ -112,4 +96,4 @@ def resolve_debug_port(config: DebugConfig, target_port: int) -> int:
11296
"""Resolve the actual port to use for debugging"""
11397
if config.auto_port:
11498
return find_available_port(target_port)
115-
return target_port
99+
return target_port

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

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from rich.console import Console
55

66
from agentex import Agentex
7+
from agentex.lib.constants.ports import TEMPORAL_ADDRESS as DEFAULT_TEMPORAL_ADDRESS
78
from agentex.lib.utils.logging import make_logger
89

910
# Import Temporal client for direct workflow termination
@@ -19,88 +20,84 @@
1920
def should_cleanup_on_restart() -> bool:
2021
"""
2122
Check if cleanup should be performed on restart.
22-
23+
2324
Returns True if:
2425
- ENVIRONMENT=development, OR
2526
- AUTO_CLEANUP_ON_RESTART=true
2627
"""
2728
env = os.getenv("ENVIRONMENT", "").lower()
2829
auto_cleanup = os.getenv("AUTO_CLEANUP_ON_RESTART", "true").lower()
29-
30+
3031
return env == "development" or auto_cleanup == "true"
3132

3233

33-
def cleanup_agent_workflows(
34-
agent_name: str,
35-
force: bool = False,
36-
development_only: bool = True
37-
) -> None:
34+
def cleanup_agent_workflows(agent_name: str, force: bool = False, development_only: bool = True) -> None:
3835
"""
3936
Clean up all running workflows for an agent during development.
40-
37+
4138
This cancels (graceful) all running tasks for the specified agent.
4239
When force=True, directly terminates workflows via Temporal client.
43-
40+
4441
Args:
4542
agent_name: Name of the agent to cleanup workflows for
4643
force: If True, directly terminate workflows via Temporal client
4744
development_only: Only perform cleanup in development environment
4845
"""
49-
46+
5047
# Safety check - only run in development mode by default
5148
if development_only and not force and not should_cleanup_on_restart():
5249
logger.warning("Cleanup skipped - not in development mode. Use --force to override.")
5350
return
54-
51+
5552
method = "terminate (direct)" if force else "cancel (via agent)"
5653
console.print(f"[blue]Cleaning up workflows for agent '{agent_name}' using {method}...[/blue]")
57-
54+
5855
try:
5956
client = Agentex()
60-
57+
6158
# Get all running tasks
6259
if agent_name:
6360
all_tasks = client.tasks.list(agent_name=agent_name)
6461
else:
6562
all_tasks = client.tasks.list()
66-
running_tasks = [task for task in all_tasks if hasattr(task, 'status') and task.status == "RUNNING"]
67-
63+
running_tasks = [task for task in all_tasks if hasattr(task, "status") and task.status == "RUNNING"]
64+
6865
if not running_tasks:
6966
console.print("[yellow]No running tasks found[/yellow]")
7067
return
71-
68+
7269
console.print(f"[blue]Cleaning up {len(running_tasks)} running task(s) for agent '{agent_name}'...[/blue]")
73-
70+
7471
successful_cleanups = 0
7572
total_tasks = len(running_tasks)
76-
73+
7774
for task in running_tasks:
7875
task_cleanup_success = False
79-
76+
8077
if force:
8178
# Force mode: Do both graceful RPC cancellation AND direct Temporal termination
8279
rpc_success = False
8380
temporal_success = False
84-
81+
8582
try:
8683
# First: Graceful cancellation via agent RPC (handles database/agent cleanup)
8784
cleanup_single_task(client, agent_name, task.id)
8885
logger.debug(f"Completed RPC cancellation for task {task.id}")
8986
rpc_success = True
9087
except Exception as e:
9188
logger.warning(f"RPC cancellation failed for task {task.id}: {e}")
92-
89+
9390
try:
9491
# Second: Direct Temporal termination (ensures workflow is forcefully stopped)
9592
asyncio.run(cleanup_single_task_direct(task.id))
9693
logger.debug(f"Completed Temporal termination for task {task.id}")
9794
temporal_success = True
9895
except Exception as e:
9996
logger.warning(f"Temporal termination failed for task {task.id}: {e}")
100-
97+
10198
# Count as success if either operation succeeded
10299
task_cleanup_success = rpc_success or temporal_success
103-
100+
104101
else:
105102
# Normal mode: Only graceful cancellation via agent RPC
106103
try:
@@ -109,21 +106,25 @@ def cleanup_agent_workflows(
109106
except Exception as e:
110107
logger.error(f"Failed to cleanup task {task.id}: {e}")
111108
task_cleanup_success = False
112-
109+
113110
if task_cleanup_success:
114111
successful_cleanups += 1
115112
logger.debug(f"Successfully cleaned up task {task.id}")
116113
else:
117114
logger.error(f"Failed to cleanup task {task.id}")
118115
# Don't increment successful_cleanups for actual failures
119-
116+
120117
if successful_cleanups == total_tasks:
121-
console.print(f"[green]✓ Successfully cleaned up all {successful_cleanups} task(s) for agent '{agent_name}'[/green]")
118+
console.print(
119+
f"[green]✓ Successfully cleaned up all {successful_cleanups} task(s) for agent '{agent_name}'[/green]"
120+
)
122121
elif successful_cleanups > 0:
123-
console.print(f"[yellow]⚠ Successfully cleaned up {successful_cleanups}/{total_tasks} task(s) for agent '{agent_name}'[/yellow]")
122+
console.print(
123+
f"[yellow]⚠ Successfully cleaned up {successful_cleanups}/{total_tasks} task(s) for agent '{agent_name}'[/yellow]"
124+
)
124125
else:
125126
console.print(f"[red]✗ Failed to cleanup any tasks for agent '{agent_name}'[/red]")
126-
127+
127128
except Exception as e:
128129
console.print(f"[red]Agent workflow cleanup failed: {str(e)}[/red]")
129130
logger.exception("Agent workflow cleanup failed")
@@ -133,51 +134,47 @@ def cleanup_agent_workflows(
133134
async def cleanup_single_task_direct(task_id: str) -> None:
134135
"""
135136
Directly terminate a workflow using Temporal client.
136-
137+
137138
Args:
138139
task_id: ID of the task (used as workflow_id)
139140
"""
140141
if TemporalClient is None:
141142
raise ImportError("temporalio package not available for direct workflow termination")
142-
143+
143144
try:
144145
# Connect to Temporal server (assumes default localhost:7233)
145-
client = await TemporalClient.connect("localhost:7233") # type: ignore
146-
146+
client = await TemporalClient.connect(DEFAULT_TEMPORAL_ADDRESS) # type: ignore
147+
147148
# Get workflow handle and terminate
148149
handle = client.get_workflow_handle(workflow_id=task_id) # type: ignore
149150
await handle.terminate() # type: ignore
150-
151+
151152
logger.debug(f"Successfully terminated workflow {task_id} via Temporal client")
152-
153+
153154
except Exception as e:
154155
# Check if the workflow was already completed - this is actually a success case
155156
if "workflow execution already completed" in str(e).lower():
156157
logger.debug(f"Workflow {task_id} was already completed - no termination needed")
157158
return # Don't raise an exception for this case
158-
159+
159160
logger.error(f"Failed to terminate workflow {task_id} via Temporal client: {e}")
160161
raise
161162

162163

163164
def cleanup_single_task(client: Agentex, agent_name: str, task_id: str) -> None:
164165
"""
165166
Clean up a single task/workflow using agent RPC cancel method.
166-
167+
167168
Args:
168-
client: Agentex client instance
169+
client: Agentex client instance
169170
agent_name: Name of the agent that owns the task
170171
task_id: ID of the task to cleanup
171172
"""
172173
try:
173174
# Use the agent RPC method to cancel the task
174-
client.agents.rpc_by_name(
175-
agent_name=agent_name,
176-
method="task/cancel",
177-
params={"task_id": task_id}
178-
)
175+
client.agents.rpc_by_name(agent_name=agent_name, method="task/cancel", params={"task_id": task_id})
179176
logger.debug(f"Successfully cancelled task {task_id} via agent '{agent_name}'")
180-
177+
181178
except Exception as e:
182179
logger.warning(f"RPC task/cancel failed for task {task_id}: {e}")
183-
raise
180+
raise

0 commit comments

Comments
 (0)