Skip to content

Fix: record started_at for background agent tasks so duration is reported#2493

Open
nankingjing wants to merge 2 commits into
MoonshotAI:mainfrom
nankingjing:fix-agent-task-started-at
Open

Fix: record started_at for background agent tasks so duration is reported#2493
nankingjing wants to merge 2 commits into
MoonshotAI:mainfrom
nankingjing:fix-agent-task-started-at

Conversation

@nankingjing

@nankingjing nankingjing commented Jul 11, 2026

Copy link
Copy Markdown

Problem

Background agent tasks never have runtime.started_at set, so their run
duration is silently lost. Background bash tasks set it correctly.

The only place started_at is ever assigned is the bash worker startup
(background/worker.py):

runtime.status = "starting"
runtime.worker_pid = os.getpid()
runtime.started_at = time.time()

Agent tasks reach the running state through BackgroundTaskManager._mark_task_running
(called from background/agent_runner.py), which set every startup field
except started_at:

runtime.status = "running"
runtime.updated_at = time.time()
runtime.heartbeat_at = runtime.updated_at
runtime.failure_reason = None

Since TaskRuntime.started_at defaults to None (background/models.py), agent
tasks keep started_at is None for their whole lifetime.

Impact

Downstream consumers guard on started_at, so for agent tasks they silently
degrade:

  • publish_terminal_notifications computes duration_s = None, so the completion
    notification omits the Duration: … line that bash tasks show.
  • _mark_task_completed / _mark_task_failed / _mark_task_timed_out /
    _mark_task_killed all gate telemetry on
    if runtime.started_at and runtime.finished_at:, which is never true for agent
    tasks — so the background_task_completed event (with duration_s) is never
    emitted for them.

Fix

Record the first transition into running as the start time in
_mark_task_running, mirroring what the bash worker does. The assignment is
guarded by started_at is None because _mark_task_running is also invoked
again after each approval is resolved; overwriting would otherwise shrink the
measured duration to the last approval gap.

runtime.status = "running"
runtime.updated_at = time.time()
if runtime.started_at is None:
    runtime.started_at = runtime.updated_at
runtime.heartbeat_at = runtime.updated_at

This has no effect on bash tasks (they don't go through _mark_task_running) and
is a no-op for tasks that already reached a terminal state (the early return
guarding terminal status is untouched).

Test

Adds test_mark_task_running_sets_started_at_once, asserting that the first
_mark_task_running sets started_at, a subsequent re-mark (e.g. after an
approval) preserves it, and completion then yields finished_at >= started_at.

Verification

  • python -m py_compile on both changed files: passes.
  • New/existing tests verified by reading; not executed in my environment
    (runtime deps such as kosong/kaos were not installable here).

Open in Devin Review

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant