Parse an OpenCode JSONL log file into an ATIF Trajectory.
(
log_path: Path,
model_name: str,
agent_name: str,
instruction: str | None = None,
)
| 337 | f"echo \"[{prefix}-loop] Run ${vp}_RUN starting (budget=${{{vp}_TIMEOUT}}s)\" | tee -a /logs/agent/{log_file}; " |
| 338 | f"{vp}_RUN_START=$(date +%s); " |
| 339 | f"timeout ${{{vp}_TIMEOUT}}s opencode --model {escaped_model} run --format=json {escaped_instruction} " |
| 340 | f"2>&1 </dev/null | tee -a /logs/agent/{log_file}; " |
| 341 | f"{vp}_RUN_DUR=$(( $(date +%s) - {vp}_RUN_START )); " |
| 342 | f"echo \"[{prefix}-loop] opencode exited after ${{{vp}_RUN_DUR}}s\" | tee -a /logs/agent/{log_file}; " |
| 343 | # Track fast failures (< 10s) to avoid spinning on broken setups |
| 344 | f"if [ ${vp}_RUN_DUR -lt 10 ]; then {vp}_FAST_FAILS=$(({vp}_FAST_FAILS + 1)); else {vp}_FAST_FAILS=0; fi; " |
| 345 | "while true; do " |
| 346 | # Consecutive fast failures usually mean a transient broken setup |
| 347 | # (locked db, API hiccup). Before giving up for good, cool down |
| 348 | # once for 60s and retry with a fresh session; abort only if the |
| 349 | # fast-fails come right back. |
| 350 | f" if [ ${vp}_FAST_FAILS -ge ${vp}_MAX_FAST_FAILS ]; then " |
| 351 | f" if [ ${vp}_COOLDOWN_USED -ge 1 ]; then " |
| 352 | f" echo \"[{prefix}-loop] ${{{vp}_FAST_FAILS}} consecutive fast failures (<10s) after cooldown, aborting\" | tee -a /logs/agent/{log_file}; " |
| 353 | " break; " |
| 354 | " fi; " |
| 355 | f" {vp}_COOLDOWN_USED=1; {vp}_FAST_FAILS=0; {vp}_RESUME=0; " |
| 356 | f" echo \"[{prefix}-loop] ${{{vp}_MAX_FAST_FAILS}} consecutive fast failures (<10s) — cooling down 60s, then retrying with a fresh session\" | tee -a /logs/agent/{log_file}; " |
| 357 | " sleep 60; " |
| 358 | " fi; " |
| 359 | f" {vp}_ELAPSED=$(( $(date +%s) - {vp}_START )); " |
| 360 | f" {vp}_REMAINING=$(( {vp}_TIMEOUT - {vp}_ELAPSED )); " |
| 361 | f" echo \"[{prefix}-loop] Elapsed: ${{{vp}_ELAPSED}}s, Remaining: ${{{vp}_REMAINING}}s\" | tee -a /logs/agent/{log_file}; " |
| 362 | f" if [ ${vp}_REMAINING -lt ${vp}_MIN_RESTART ]; then " |
| 363 | f" echo \"[{prefix}-loop] Less than ${{{vp}_MIN_RESTART}}s remaining, stopping restart loop\" | tee -a /logs/agent/{log_file}; " |
| 364 | " break; " |
| 365 | " fi; " |
| 366 | f" {vp}_RUN=$(({vp}_RUN + 1)); " |
| 367 | # Resume the previous session (context preserved) when possible: |
| 368 | # pull the newest sessionID out of this session's own JSON log. |
| 369 | f" {vp}_SID=''; " |
| 370 | f" if [ ${vp}_RESUME -eq 1 ]; then {vp}_SID=$(grep -o '\"sessionID\":\"[^\"]*\"' /logs/agent/{log_file} | tail -1 | cut -d'\"' -f4); fi; " |
| 371 | f" echo \"[{prefix}-loop] Run ${vp}_RUN starting (${{{vp}_REMAINING}}s remaining)\" | tee -a /logs/agent/{log_file}; " |
| 372 | f" {vp}_RUN_START=$(date +%s); " |
| 373 | f" if [ -n \"${vp}_SID\" ]; then " |
| 374 | f" echo \"[{prefix}-loop] Resuming session ${vp}_SID\" | tee -a /logs/agent/{log_file}; " |
| 375 | f" timeout ${{{vp}_REMAINING}}s opencode --model {escaped_model} run --format=json --session \"${vp}_SID\" {resume_message} " |
| 376 | f" 2>&1 </dev/null | tee -a /logs/agent/{log_file}; " |
| 377 | " else " |
| 378 | f" timeout ${{{vp}_REMAINING}}s opencode --model {escaped_model} run --format=json {continue_instruction} " |
| 379 | f" 2>&1 </dev/null | tee -a /logs/agent/{log_file}; " |
| 380 | " fi; " |
| 381 | f" {vp}_RUN_DUR=$(( $(date +%s) - {vp}_RUN_START )); " |
| 382 | f" echo \"[{prefix}-loop] opencode exited after ${{{vp}_RUN_DUR}}s\" | tee -a /logs/agent/{log_file}; " |
| 383 | # A fast-failing resume likely means the stored session is bad |
| 384 | # (locked db, corrupt state) — fall back to a fresh session. |
| 385 | f" if [ ${vp}_RUN_DUR -lt 10 ]; then " |
| 386 | f" {vp}_FAST_FAILS=$(({vp}_FAST_FAILS + 1)); " |
| 387 | f" if [ -n \"${vp}_SID\" ]; then {vp}_RESUME=0; echo \"[{prefix}-loop] Resume failed fast — next run starts a fresh session\" | tee -a /logs/agent/{log_file}; fi; " |
| 388 | f" else {vp}_FAST_FAILS=0; {vp}_RESUME=1; fi; " |
| 389 | "done; " |
| 390 | f"echo \"[{prefix}-loop] Finished after ${vp}_RUN runs\" | tee -a /logs/agent/{log_file}" |
| 391 | ) |
| 392 | |
| 393 | def _agent_env(self) -> dict[str, str]: |
| 394 | env = self._resolve_api_key_env() |
| 395 | env["OPENCODE_YOLO"] = "true" |
| 396 | env["OPENCODE_DANGEROUSLY_SKIP_PERMISSIONS"] = "true" |
no test coverage detected