Link a trial's thread while the agent still runs

cff5e4b62f0f · AtlantisPleb · · parent 20ee7553ecd9

Link a trial's thread while the agent still runs

The adapter parsed the [oa:thread …] announcement only after the agent
phase, so a trial's transcript became watchable on /gym exactly when there
was nothing left to watch. The container writes coder.txt through Harbor's
host mirror, so a watcher task now polls it and posts the thread link the
moment the coder announces — the finally report stays as the backstop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfZq5s3rc6zpnBR75pTQaU
Co-Authored-By
Claude Fable 5 <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified bench/adapters/openagents_coder.py

Diff

1 file changed, +29 -9

bench/adapters/openagents_coder.py modified +29 -9

@@ -26,12 +26,15 @@ the trial's `trajectory.json`.

26 26
When `OPENAGENTS_GYM_RUN_ID` is set (`bench/run-suite.sh` registers the run
27 27
against the Gym lifecycle API, OpenAgentsInc/openagents#38), the adapter
28 28
reports each trial to the run from the host side: state `running` at
29
agent-phase start, and again after the agent phase with the thread id parsed
30
from the captured coder output (`coder.txt`, the `[oa:thread <uuid>]` line
31
the coder prints in `--plain` mode). Reporting never fails a trial: a
32
refused or unreachable Gym is logged and the trial continues.
29
agent-phase start, the thread link as soon as the coder announces it (a
30
watcher polls the host-mirrored `coder.txt` for the `[oa:thread <uuid>]`
31
line while the agent runs, so the transcript is watchable live on /gym),
32
and a final backstop report after the agent phase. Reporting never fails a
33
trial: a refused or unreachable Gym is logged and the trial continues.
33 34
"""
34 35
36
import asyncio
37
import contextlib
35 38
import json
36 39
import os
37 40
import re

@@ -233,12 +236,26 @@ class OpenAgentsCoder(BaseInstalledAgent):

233 236
                "OPENAGENTS_CODER_OLLAMA_HOST", "http://host.docker.internal:11434"
234 237
            )
235 238
236
        # The trial exists before the agent phase does anything, and after it
237
        # the coder's captured output names the thread the session opened —
238
        # on both lanes now that the local lane reports too (#39). The second
239
        # report runs in `finally` so a failed agent phase still links its
240
        # trial before the exception continues to Harbor.
239
        # The trial exists before the agent phase does anything, and the
240
        # thread link lands while the agent still works: the container writes
241
        # `coder.txt` through Harbor's host mirror, so the `[oa:thread …]`
242
        # announcement is greppable on the host within seconds of the session
243
        # opening, and posting it then is what makes the trial's transcript
244
        # watchable live on /gym rather than only after the trial ends. The
245
        # `finally` report stays as the backstop — a session that never
246
        # announced (offline lane) or a watcher that lost a race still links
247
        # its trial before the exception continues to Harbor.
241 248
        self._report_trial("running")
249
250
        async def _link_when_announced() -> None:
251
            while True:
252
                thread_id = self._thread_id_from_log()
253
                if thread_id:
254
                    await asyncio.to_thread(self._report_trial, "running", thread_id)
255
                    return
256
                await asyncio.sleep(2)
257
258
        watcher = asyncio.create_task(_link_when_announced())
242 259
        try:
243 260
            await self.exec_as_agent(
244 261
                environment,

@@ -246,4 +263,7 @@ class OpenAgentsCoder(BaseInstalledAgent):

246 263
                env=env,
247 264
            )
248 265
        finally:
266
            watcher.cancel()
267
            with contextlib.suppress(asyncio.CancelledError):
268
                await watcher
249 269
            self._report_trial("running", self._thread_id_from_log())

This page updates live while a promote is in flight · changelog