feat: add prompt-first batches and pausable task guidance
This commit is contained in:
1 parent
cec9898e4c
commit
057f0c3050
44 files changed
+1080
-191
No files matched your search
@@ -302,7 +302,7 @@ class TaskQueueService:
|
||||
normalized = current_status.lower().strip() if isinstance(current_status, str) else None
|
||||
if normalized == "success":
|
||||
return "completed", True
|
||||
if normalized in {"completed", "failed", "cancelled"}:
|
||||
if normalized in {"completed", "failed", "cancelled", "interrupted"}:
|
||||
return normalized, False
|
||||
return ("completed" if returncode == 0 else "failed"), True
|
||||
|
||||
@@ -388,10 +388,9 @@ class TaskQueueService:
|
||||
@classmethod
|
||||
def _dispatch_pending_tasks(cls) -> None:
|
||||
"""Launch every pending task admissible under the current concurrency limit."""
|
||||
cls._sync_control_states()
|
||||
limit = cls._concurrency_limit()
|
||||
state.prune_finished_runs()
|
||||
if limit == 1 and state.is_running:
|
||||
return
|
||||
|
||||
# Dispatched-but-not-yet-spawned runs are only visible as queue items in
|
||||
# "running" state, so admission must count those too -- active_runs alone
|
||||
@@ -410,7 +409,9 @@ class TaskQueueService:
|
||||
and item.get("pid") not in active_pids
|
||||
for item in in_flight
|
||||
)
|
||||
capacity = limit - len(state.active_runs) - starting
|
||||
capacity = (
|
||||
limit - sum(not r.get("parked") for r in state.active_runs.values()) - starting
|
||||
)
|
||||
if capacity <= 0:
|
||||
return
|
||||
busy_devices = state.busy_device_ids | {
|
||||
@@ -432,11 +433,15 @@ class TaskQueueService:
|
||||
# A task without a resolved device may bind to any serial, so it
|
||||
# only launches on an otherwise idle scheduler; the device lock
|
||||
# then allocates freely without contending against active runs.
|
||||
if device is None and (state.active_runs or in_flight or dispatched_any):
|
||||
if device is None and (
|
||||
any(not r.get("parked") for r in state.active_runs.values())
|
||||
or in_flight
|
||||
or dispatched_any
|
||||
):
|
||||
continue
|
||||
if device is not None and target.lock_key in busy_devices:
|
||||
continue
|
||||
elif limit > 1 and device is not None and target.lock_key in busy_devices:
|
||||
elif limit >= 1 and device is not None and target.lock_key in busy_devices:
|
||||
# A second worker for this device would wait on its lock.
|
||||
continue
|
||||
|
||||
@@ -454,6 +459,42 @@ class TaskQueueService:
|
||||
if capacity <= 0:
|
||||
break
|
||||
|
||||
@classmethod
|
||||
def _sync_control_states(cls) -> None:
|
||||
from artemis.runtime.task_control import read_state
|
||||
from apps.admin_console.services.automation_store import store
|
||||
|
||||
for item in state.queue_items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
sid = str(item.get("session_id") or "")
|
||||
run = state.active_runs.get(sid)
|
||||
if not run:
|
||||
continue
|
||||
status = read_state(sid).get("status")
|
||||
if status not in {"running", "task_paused", "resuming"}:
|
||||
continue
|
||||
old = item.get("status")
|
||||
if status == old:
|
||||
continue
|
||||
parked = status in {"task_paused", "resuming"}
|
||||
now = time.time()
|
||||
if parked and not run.get("parked"):
|
||||
run["non_execution_started_at"] = now
|
||||
elif not parked and run.get("parked"):
|
||||
run["non_execution_seconds"] = (
|
||||
float(run.get("non_execution_seconds") or 0)
|
||||
+ now
|
||||
- float(run.pop("non_execution_started_at", now))
|
||||
)
|
||||
run["parked"] = parked
|
||||
item["status"] = status
|
||||
job = store.job(sid)
|
||||
if job and job.get("status") != "finalizing":
|
||||
job["status"] = status
|
||||
store.save_job(job)
|
||||
cls._broadcast_event("task_status", {"session_id": sid, "status": status})
|
||||
|
||||
@classmethod
|
||||
def _begin_task_run(
|
||||
cls,
|
||||
@@ -531,6 +572,22 @@ class TaskQueueService:
|
||||
env["ARTEMIS_SESSION_ID"] = str(sess_id)
|
||||
env["ARTEMIS_TASK_INGRESS"] = str(task_item.get("ingress", "frontend"))
|
||||
env["ARTEMIS_TASK_WORKER"] = "1"
|
||||
overrides = task_item.get("runtime_overrides") or {}
|
||||
for option, variable in {
|
||||
"model": "OPENAI_MODEL",
|
||||
"reasoning_effort": "OPENAI_REASONING_EFFORT",
|
||||
"request_timeout_seconds": "OPENAI_TIMEOUT_SECONDS",
|
||||
"max_retries": "OPENAI_MAX_RETRIES",
|
||||
"retry_interval_seconds": "OPENAI_RETRY_INTERVAL_SECONDS",
|
||||
"record_video": "ARTEMIS_RECORDING_ENABLED",
|
||||
"analyze_video": "ARTEMIS_VIDEO_ENABLED",
|
||||
}.items():
|
||||
if overrides.get(option) is not None:
|
||||
env[variable] = (
|
||||
str(overrides[option]).lower()
|
||||
if isinstance(overrides[option], bool)
|
||||
else str(overrides[option])
|
||||
)
|
||||
target.endpoint.apply_to_environment(env)
|
||||
env[DeviceExecutionLock.LOCK_SCOPE_ENV] = target.lock_scope
|
||||
queue_ticket = task_item.get("queue_ticket")
|
||||
@@ -664,6 +721,12 @@ class TaskQueueService:
|
||||
returncode,
|
||||
manual_stop,
|
||||
)
|
||||
if (
|
||||
state.is_shutting_down
|
||||
and not manual_stop
|
||||
and current_status not in {"completed", "success", "failed", "interrupted"}
|
||||
):
|
||||
new_status, should_persist = "interrupted", True
|
||||
if should_persist:
|
||||
if session_repo.update_session_status(sess_id, new_status, time.time()):
|
||||
print(f"[QueueWorker] Updated session {sess_id} status to '{new_status}'")
|
||||
@@ -1069,6 +1132,7 @@ class TaskQueueService:
|
||||
conversation_id: str | None = None,
|
||||
verification_level: str | None = None,
|
||||
explorer_mode: str | None = None,
|
||||
runtime_overrides: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Enqueues one or more goals and wakes up the background worker.
|
||||
|
||||
@@ -1123,6 +1187,8 @@ class TaskQueueService:
|
||||
verification_level=verification_level,
|
||||
explorer_mode=explorer_mode,
|
||||
)
|
||||
if runtime_overrides:
|
||||
task_item["runtime_overrides"] = runtime_overrides
|
||||
state.queue_items.append(task_item)
|
||||
enqueued_tasks.append(task_item)
|
||||
cls._broadcast_startup_progress(
|
||||
|
||||
Reference in new issue
Block a user