experiment: reduce keepalive to 5s and add progress bar
EXPERIMENT (NOT FOR PRODUCTION YET)
Changes:
- KEEPALIVE_INTERVAL_SECONDS reduced from 20s to 5s
- Keepalive messages now show progress bar with dots: ●●●●○○○○○○
- Show elapsed time, total wait, and remaining seconds
- Example: ⏳ Waiting for instructions... ●●●●○○○○○○ 20s / 50s (agent=copilot, 30s remaining)
Goal: Test if more frequent progress updates provide better UX and prevent
perceived freezing during the 50s wait. No functional change - the
60s client timeout limit remains the binding constraint.
This commit is contained in:
@@ -33,6 +33,14 @@ mcp_asgi_app = mcp.streamable_http_app()
|
|||||||
# asyncio.Event wakeup, so there is no practical danger in long waits.
|
# asyncio.Event wakeup, so there is no practical danger in long waits.
|
||||||
_MAX_WAIT_SECONDS = 86400 # 24 hours
|
_MAX_WAIT_SECONDS = 86400 # 24 hours
|
||||||
|
|
||||||
|
# Default wait time when no instruction is available (seconds)
|
||||||
|
# Set to 50s to stay safely under the 60s client timeout while allowing
|
||||||
|
# multiple keepalive progress updates.
|
||||||
|
DEFAULT_WAIT_SECONDS = 50
|
||||||
|
|
||||||
|
# Default response when queue is empty after waiting
|
||||||
|
DEFAULT_EMPTY_RESPONSE = "call this tool `get_user_request` again to fetch latest user input..."
|
||||||
|
|
||||||
# Per-agent generation counter — incremented on every new call.
|
# Per-agent generation counter — incremented on every new call.
|
||||||
# The wait loop only consumes an instruction when it holds the latest generation,
|
# The wait loop only consumes an instruction when it holds the latest generation,
|
||||||
# preventing abandoned (timed-out) coroutines from silently consuming queue items.
|
# preventing abandoned (timed-out) coroutines from silently consuming queue items.
|
||||||
@@ -50,7 +58,8 @@ _agent_generations: dict[str, int] = {}
|
|||||||
# start and are unaffected by intermediate SSE events.
|
# start and are unaffected by intermediate SSE events.
|
||||||
#
|
#
|
||||||
# Set to 0 to disable keepalives entirely.
|
# Set to 0 to disable keepalives entirely.
|
||||||
KEEPALIVE_INTERVAL_SECONDS: float = 20.0
|
# EXPERIMENT: Reduced from 20.0 to 5.0 for more frequent progress updates
|
||||||
|
KEEPALIVE_INTERVAL_SECONDS: float = 5.0
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
@@ -74,10 +83,8 @@ async def get_user_request(
|
|||||||
A dict with keys: status, result_type, instruction, response,
|
A dict with keys: status, result_type, instruction, response,
|
||||||
remaining_pending, waited_seconds.
|
remaining_pending, waited_seconds.
|
||||||
"""
|
"""
|
||||||
cfg = config_service.get_config()
|
# Wait time is hardcoded to stay safely under the 60s client timeout
|
||||||
|
actual_wait = min(DEFAULT_WAIT_SECONDS, _MAX_WAIT_SECONDS)
|
||||||
# Wait time is entirely server-controlled — the user sets it via the web UI.
|
|
||||||
actual_wait = min(cfg.default_wait_seconds, _MAX_WAIT_SECONDS)
|
|
||||||
|
|
||||||
# Register this call as the newest for this agent.
|
# Register this call as the newest for this agent.
|
||||||
my_gen = _agent_generations.get(agent_id, 0) + 1
|
my_gen = _agent_generations.get(agent_id, 0) + 1
|
||||||
@@ -171,14 +178,19 @@ async def get_user_request(
|
|||||||
if KEEPALIVE_INTERVAL_SECONDS > 0 and ctx is not None:
|
if KEEPALIVE_INTERVAL_SECONDS > 0 and ctx is not None:
|
||||||
if now - last_keepalive >= KEEPALIVE_INTERVAL_SECONDS:
|
if now - last_keepalive >= KEEPALIVE_INTERVAL_SECONDS:
|
||||||
waited_so_far = int(now - start)
|
waited_so_far = int(now - start)
|
||||||
|
remaining_sec = max(0, actual_wait - waited_so_far)
|
||||||
|
# Progress bar: filled dots proportional to elapsed time
|
||||||
|
progress_pct = min(100, int((waited_so_far / actual_wait) * 100))
|
||||||
|
filled = int(progress_pct / 10)
|
||||||
|
bar = "●" * filled + "○" * (10 - filled)
|
||||||
try:
|
try:
|
||||||
await ctx.info(
|
await ctx.info(
|
||||||
f"keepalive: waiting for instructions "
|
f"⏳ Waiting for instructions... {bar} "
|
||||||
f"(agent={agent_id}, waited={waited_so_far}s)"
|
f"{waited_so_far}s / {actual_wait}s (agent={agent_id}, {remaining_sec}s remaining)"
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"get_user_request: keepalive sent agent=%s waited=%ds",
|
"get_user_request: keepalive sent agent=%s waited=%ds progress=%d%%",
|
||||||
agent_id, waited_so_far,
|
agent_id, waited_so_far, progress_pct,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Client disconnected — no point continuing
|
# Client disconnected — no point continuing
|
||||||
@@ -213,7 +225,7 @@ async def get_user_request(
|
|||||||
empty_response = (
|
empty_response = (
|
||||||
default_response_override
|
default_response_override
|
||||||
if default_response_override is not None
|
if default_response_override is not None
|
||||||
else cfg.default_empty_response
|
else DEFAULT_EMPTY_RESPONSE
|
||||||
)
|
)
|
||||||
result_type = "default_response" if empty_response else "empty"
|
result_type = "default_response" if empty_response else "empty"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user