diff --git a/agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md b/agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md index 9fe2970..126ac28 100644 --- a/agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md +++ b/agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md @@ -112,7 +112,7 @@ When recovering a KST-night `local-G07`–`local-G08` Laguna locator or a termin - Keep exactly one `agent-task/{task_group}/WORK_LOG.md` per task group. Do not create one in a split-subtask directory. - Allow only the dispatcher to modify this file. Worker/self-check/review models need not read or update it, and success must not depend on its prose. - Append chronological `START`/`FINISH` rows with time, task, role, attempt, model, result, and locator. Record time in KST (`UTC+09:00`) as `YY-MM-DD HH:MM:SS`, for example `26-07-26 07:40:15`. Use this single timeline to inspect parallel execution order. -- When code-review moves a PASS task, do not move, copy, or delete the task-group `WORK_LOG.md`. For a single task, create the archive destination and move every artifact except `WORK_LOG.md`, preserving the path where the dispatcher writes the final `FINISH`. +- Do not require the common code-review skill to preserve `WORK_LOG.md`. For split work the group log normally remains in the parent because review moves only the selected subtask. For a single task review may move the log with the task archive; after review exits, resolve exactly one source from the active group path or verified completed archive and normalize it to `work_log_N.log`. - After every observed task in a task group has a verified complete archive and no active/running task remains, append the final `FINISH` and move the generated `WORK_LOG.md` under the final completed archive's group root as `work_log_N.log`. If an archive exists after restart but the last `START` lacks `FINISH`, do not terminate or archive while any PID/start token, per-attempt process marker, or pidless stream/native evidence remains live. Track it until execution evidence has ended and the complete archive is verified, then append `FINISH` with `reconciled:verified-complete-archive` and move the log. Use `agent-task/archive/YYYY/MM/{task_group}/` for split tasks and the actual suffix-bearing archive destination for a single task. Set `N` to one more than the maximum suffix for the same task group across all months, starting at `0`. - If `WORK_LOG.md` archiving fails or multiple active/archive sources exist, drain other independent work and return non-terminal exit `3` for retry. Return successful exit `0` only after a completed group that generated a log has no active `WORK_LOG.md` and its `work_log_N.log` is verified. Keep an incomplete group's `WORK_LOG.md` active for blocker or exit `3` recovery. - Split each attempt locator into `stream.log` for model stdout/stderr and `heartbeat.log` for dispatcher state. Determine health only from the newest progress in `stream.log` and native session events; never use heartbeat mtime as progress evidence. Do not copy either log into `WORK_LOG.md`. @@ -192,7 +192,7 @@ When recovering a KST-night `local-G07`–`local-G08` Laguna locator or a termin - Send an already completed review stub with no dispatcher execution record to review. Never send dispatcher-recorded Pi worker success to review before self-check completes. - Start official review and worker/self-check together when they belong to different dependency-ready tasks. Do not wait for another task's checkout or write-set. - Let the dispatcher record every worker/self-check/review attempt start and finish in the task-group `WORK_LOG.md`. - - Archive `WORK_LOG.md` as `work_log_N.log` only after the final task review process exits, the dispatcher appends `FINISH`, and a complete scan finds no active/running task in that group. The code-review process must not move it first. + - Archive `WORK_LOG.md` as `work_log_N.log` only after the final task review process exits, the dispatcher appends `FINISH`, and a complete scan finds no active/running task in that group. Accept the log at either the active group path or the verified completed single-task archive; do not impose either location contract on common plan/code-review. 3. **Escalate and recover context.** - Escalate `agy -> Claude -> Codex` or `Claude -> Codex` only on terminal provider error events or stderr evidence of context/output limits, provider quota/rate limits, unavailable models, or confirmed provider transport errors. For AGY, accept top-level `error`, `fatal`, `request.failed`, or `turn.failed` events; failed/rejected status with a top-level error/code; stderr; or strong `RESOURCE_EXHAUSTED`, HTTP 429, quota, or rate-limit evidence in `agy-cli.log`. For Claude, classify a `rate_limit_event` with `rate_limit_info.status=rejected`, an error `result` with `api_error_status=429` or `error=rate_limit`, or a `You've hit your session limit · resets ...` terminal diagnostic as `provider-quota`. Never escalate from an assistant message, source text, tool/test output, or a plain quota-configuration string in an AGY log. diff --git a/agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py b/agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py index 2d41eda..531e410 100644 --- a/agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py +++ b/agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py @@ -3353,7 +3353,8 @@ class ReviewControlTest(unittest.TestCase): self.assertEqual(priority.count("Allow `final`"), 2) self.assertIn("unless at least one of the two titled permissions", priority) self.assertNotIn("unless exactly one of the two titled permissions", priority) - self.assertNotIn("`final`", lower_contract) + self.assertNotIn("### `final` Permission", lower_contract) + self.assertNotIn("Allow `final`", lower_contract) self.assertIn( "### Every Other User-Visible Message Must Use `commentary`", priority, @@ -3477,7 +3478,7 @@ class ReviewControlTest(unittest.TestCase): skill, ) - def test_work_log_archive_ownership_is_consistent_across_skills(self): + def test_work_log_archive_ownership_stays_project_local(self): skills_root = Path(__file__).parents[3] dispatcher_skill = ( Path(__file__).parents[1] / "SKILL.md" @@ -3503,19 +3504,14 @@ class ReviewControlTest(unittest.TestCase): dispatcher_skill, ) self.assertIn( - "task-group `agent-task/{task_group}/WORK_LOG.md`는 " - "이동·복사·삭제·이름 변경하지 않는다", - review_skill, - ) - self.assertIn( - "review process 종료 뒤 dispatcher가 마지막 `FINISH`를 append", - review_skill, - ) - self.assertIn( - "dispatcher가 마지막 `FINISH` 기록과 group 완료 확인 뒤 " - "`work_log_N.log`로 archive한다", - plan_skill, + "Do not require the common code-review skill to preserve " + "`WORK_LOG.md`", + dispatcher_skill, ) + self.assertNotIn("WORK_LOG", review_skill) + self.assertNotIn("work-log", review_skill) + self.assertNotIn("WORK_LOG", plan_skill) + self.assertNotIn("work-log", plan_skill) class ProcessTerminationTest(unittest.IsolatedAsyncioTestCase): @@ -5137,7 +5133,7 @@ class WorkLogArchiveTest(unittest.TestCase): [], ) - def test_normalizes_legacy_work_log_already_moved_by_review(self): + def test_normalizes_single_task_work_log_moved_by_generic_review(self): with tempfile.TemporaryDirectory() as temporary: workspace = Path(temporary) archive = self.complete_archive(workspace, "single")