From 80fc6324fcae493a539286424809075764355523 Mon Sep 17 00:00:00 2001 From: toki Date: Sat, 8 Aug 2026 13:24:02 +0900 Subject: [PATCH] sync: to agentic-framework v1.1.192 --- agent-ops/.version | 2 +- .../orchestrate-agent-task-loop/SKILL.md | 2 +- .../scripts/dispatch.py | 24 ++++++++++--------- .../scripts/select_execution_target.py | 10 ++++---- .../tests/test_dispatch.py | 12 +++++++++- .../tests/test_select_execution_target.py | 7 +++++- 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/agent-ops/.version b/agent-ops/.version index 91bb52b5..df870776 100644 --- a/agent-ops/.version +++ b/agent-ops/.version @@ -1 +1 @@ -1.1.191 +1.1.192 diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md b/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md index b5481abf..3dc49ce5 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md @@ -112,7 +112,7 @@ Accept self-check completion only when `## Implementation Checklist` or its supp ## Work log - Keep one dispatcher-owned `WORK_LOG.md` per task group. -- Append chronological `START` and `FINISH` rows with UTC time, task artifact, plan loop, role, attempt, selected agent/model display, result, and locator. +- Append chronological `START` and `FINISH` rows with KST (`Asia/Seoul`) time, task artifact, plan loop, role, attempt, selected agent/model display, result, and locator. - Archive the group log as the next `work_log_N.log` only after every observed task in the group is verified complete and idle. - Work-log write or archive failure is a retryable control-plane failure and prevents exit `0`. diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py index e9b0443a..2ae81fb2 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py @@ -17,9 +17,10 @@ import subprocess import sys import uuid from dataclasses import dataclass, field -from datetime import datetime, timezone +from datetime import datetime from pathlib import Path from typing import Any +from zoneinfo import ZoneInfo _OBSERVATION_MODULE_NAME = "agent_task_dispatcher_observation" @@ -152,7 +153,8 @@ DISPATCHER_CHILD_BOUNDARY_PROMPT = ( ) REPOSITORY_LANGUAGE_PROMPT = "Follow the repository's language and output rules." SELF_CHECK_PROMPT_PREFIX = REPOSITORY_LANGUAGE_PROMPT -UTC = timezone.utc +DISPATCHER_TIMEZONE_NAME = "Asia/Seoul" +KST = ZoneInfo(DISPATCHER_TIMEZONE_NAME) DEFAULT_MAX_PARALLEL = 3 @@ -245,11 +247,11 @@ class ExecutionDecisionError(RuntimeError): def now_iso() -> str: - return datetime.now(timezone.utc).isoformat() + return datetime.now(KST).isoformat() -def work_log_now_utc() -> str: - return datetime.now(UTC).strftime("%y-%m-%d %H:%M:%SZ") +def work_log_now_kst() -> str: + return datetime.now(KST).strftime("%y-%m-%d %H:%M:%S KST") def sha256_file(path: Path | None) -> str: @@ -452,7 +454,7 @@ def append_work_log_event( return str(value).replace("|", r"\|").replace("\n", " ") stream.write( - f"| {sequence} | {work_log_now_utc()} | {cell(event)} | " + f"| {sequence} | {work_log_now_kst()} | {cell(event)} | " f"{cell(task_name)} | " f"{loop} | {cell(role)} | {attempt} | {cell(model)} | {cell(result)} | " f"{cell(locator.resolve())} |\n" @@ -1773,7 +1775,7 @@ def select_execution_decision( transition = "resume" if prior_decision is not None else "initial" return selector.select_execution_target( _decision_file(task, stage), stage=stage, - evaluated_at=evaluated_at or datetime.now(UTC), + evaluated_at=evaluated_at or datetime.now(KST), catalog_path=EXECUTION_CATALOG_PATH, transition=transition, prior_decision=prior_decision, @@ -1842,7 +1844,7 @@ def synthesized_official_review_decision( task: Task, *, evaluated_at: datetime | None = None ) -> dict[str, Any]: lane, grade, work_unit_id = official_review_source_identity(task) - evaluated = evaluated_at or datetime.now(UTC) + evaluated = evaluated_at or datetime.now(KST) if evaluated.tzinfo is None or evaluated.utcoffset() is None: raise ExecutionDecisionError( "official review evaluated_at이 timezone-aware가 아니다" @@ -2836,7 +2838,7 @@ def external_active_is_live( for path in root.glob("**/*.jsonl") ] native = max(sessions, key=lambda path: path.stat().st_mtime_ns) if sessions else None - now = datetime.now(timezone.utc).timestamp() + now = datetime.now(KST).timestamp() runtime = locator.get("runtime") monitor_native_session = bool( isinstance(runtime, dict) and runtime.get("native_session_monitor") @@ -3098,7 +3100,7 @@ async def invoke( resume_locator: Path | None = None, ) -> tuple[int, str | None, Path]: attempt, identity = next_execution_identity(store, task, role) - attempt_dir = store.runs / f"{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}__{identity}" + attempt_dir = store.runs / f"{datetime.now(KST).strftime('%Y%m%dT%H%M%S%z')}__{identity}" attempt_dir.mkdir(parents=True, exist_ok=False) locator_path = attempt_dir / "locator.json" stream_path = attempt_dir / "stream.log" @@ -5845,7 +5847,7 @@ async def dispatch_with_store( ): ready.append((task, stage)) - admission_time = datetime.now(UTC) + admission_time = datetime.now(KST) if args.dry_run: candidates, deferred, _ = select_dispatch_candidates( store, diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/select_execution_target.py b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/select_execution_target.py index cb58afd4..4dbb2b59 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/select_execution_target.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/select_execution_target.py @@ -13,8 +13,9 @@ import json import os import re import sys -from datetime import datetime, timezone +from datetime import datetime from pathlib import Path +from zoneinfo import ZoneInfo SCHEMA_VERSION = "2.0" @@ -24,7 +25,8 @@ DEFAULT_CATALOG_PATH = ( / "assets" / "default-execution-catalog.json" ) -TIMEZONE_NAME = "UTC" +TIMEZONE_NAME = "Asia/Seoul" +KST = ZoneInfo(TIMEZONE_NAME) _FILENAME_RE = re.compile(r"^(PLAN|CODE_REVIEW)-(local|cloud)-G(\d{2})\.md$") _MILESTONE_TASK_ID_PATTERN = r"[A-Za-z0-9]+(?:[-_+=][A-Za-z0-9]+){0,3}" _MILESTONE_TASK_ID_RE = re.compile(rf"\A{_MILESTONE_TASK_ID_PATTERN}\Z") @@ -317,7 +319,7 @@ def _base_decision( "rule_id": route.rule_id, "policy_priority": route.policy_priority, "reason_codes": list(route.reason_codes), - "evaluated_at": evaluated_at.astimezone(timezone.utc).isoformat(), + "evaluated_at": evaluated_at.astimezone(KST).isoformat(), "timezone": TIMEZONE_NAME, "time_window": route.time_window, "pinned": pinned, @@ -448,7 +450,7 @@ def select_execution_target( stage=inferred_stage, lane=lane, grade=grade, - evaluated_at=evaluated_at or datetime.now(timezone.utc), + evaluated_at=evaluated_at or datetime.now(KST), catalog_path=catalog_path, transition=transition, prior_decision=prior_decision, diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py index f248a3ee..4a4af105 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py @@ -7,7 +7,7 @@ import stat import subprocess import sys import unittest -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from pathlib import Path from tempfile import TemporaryDirectory from unittest import mock @@ -98,6 +98,16 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase): def tearDown(self): dispatch.EXECUTION_CATALOG_PATH = self.previous_catalog + def test_dispatcher_default_timestamps_use_kst(self): + timestamp = datetime.fromisoformat(dispatch.now_iso()) + + self.assertEqual(dispatch.DISPATCHER_TIMEZONE_NAME, "Asia/Seoul") + self.assertEqual(timestamp.utcoffset(), timedelta(hours=9)) + self.assertRegex( + dispatch.work_log_now_kst(), + r"^\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} KST$", + ) + def test_agent_spec_is_loaded_from_persisted_catalog_evidence(self): with TemporaryDirectory() as tmp: root = Path(tmp) diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_select_execution_target.py b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_select_execution_target.py index d6b8f143..580ef39e 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_select_execution_target.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_select_execution_target.py @@ -4,7 +4,7 @@ import os import subprocess import sys import unittest -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from pathlib import Path from tempfile import TemporaryDirectory from unittest import mock @@ -189,6 +189,11 @@ class SelectorTests(unittest.TestCase): self.assertEqual(result["selected"]["model"], "model-one") self.assertEqual(result["catalog"]["source"], str(catalog.resolve())) self.assertEqual([item["target_id"] for item in result["candidates"]], ["first", "second"]) + self.assertEqual(result["decision"]["timezone"], "Asia/Seoul") + self.assertEqual( + datetime.fromisoformat(result["decision"]["evaluated_at"]).utcoffset(), + timedelta(hours=9), + ) self.assertNotIn("quota", result) self.assertTrue(all("quota_status" not in item for item in result["candidates"]))