From 84bdf90ec9662288ab526c94e2a84de97d00826c Mon Sep 17 00:00:00 2001 From: toki Date: Fri, 14 Aug 2026 07:56:12 +0900 Subject: [PATCH] sync: agent-ops from agentic-framework v1.1.204 --- agent-ops/skills/common/code-review/SKILL.md | 2 +- .../skills/common/create-roadmap/SKILL.md | 1 + .../orchestrate-agent-task-loop/SKILL.md | 4 +- .../scripts/dispatch.py | 100 ++++++++++++------ .../tests/test_dispatch.py | 26 ++++- agent-ops/skills/common/plan/SKILL.md | 2 +- .../skills/common/update-roadmap/SKILL.md | 2 +- 7 files changed, 95 insertions(+), 42 deletions(-) diff --git a/agent-ops/skills/common/code-review/SKILL.md b/agent-ops/skills/common/code-review/SKILL.md index c33d790..f4ae99b 100644 --- a/agent-ops/skills/common/code-review/SKILL.md +++ b/agent-ops/skills/common/code-review/SKILL.md @@ -164,7 +164,7 @@ The diff is the starting point, not the boundary. Follow behavior and API connec Review scope control: - Use the plan's commands and checkpoints as the primary evidence. Add one focused, possibly table-driven reproducer only when needed to prove a suspected blocking defect; do not build speculative exhaustive probe matrices. -- Exclude unrequested generalization, future-proofing, cleanup, and architectural expansion from Required/Suggested findings unless an explicit acceptance criterion or concrete failing case makes them necessary. +- **NO OVERENGINEERING** — Do not require or propose anything beyond the user request and correctness. - Execute the applicable plan verification commands and any focused reproducer needed for the verdict. Treat implementation-owned output as a handoff and comparison source, not as a substitute for fresh reviewer verification. If recorded output is absent or insufficient but the command is available and safe in the current authorized environment, run it and repair `Verification Results` before classifying findings. If a check fails, collect enough source/runtime data to establish the root cause and one implementable fix; never emit a diagnostic-only finding that asks the next worker to investigate or choose among alternatives. - In a follow-up review, keep Required findings within the current plan, inherited Required findings, direct regressions from the fix, and concrete violations of the original SDD or contract acceptance criteria. Exclude unrelated pre-existing work from the verdict and Required/Suggested/Nit counts; mention it only in the final report as an out-of-scope task candidate. - Before adding a new Required that the current plan did not state, cite the exact original plan/SDD/contract criterion it violates or provide a concrete failing case. Do not require a preferred test shape when existing deterministic evidence proves the same behavior. diff --git a/agent-ops/skills/common/create-roadmap/SKILL.md b/agent-ops/skills/common/create-roadmap/SKILL.md index 695145a..a4da179 100644 --- a/agent-ops/skills/common/create-roadmap/SKILL.md +++ b/agent-ops/skills/common/create-roadmap/SKILL.md @@ -85,6 +85,7 @@ agent-roadmap/ ## 작성 규칙 +- **NO OVERENGINEERING** — Do not add anything beyond the user request and required behavior. - 기본 작성 언어는 한국어다. - 상태 표기는 `[스케치]`, `[계획]`, `[진행중]`, `[검토중]`, `[완료]`, `[보류]`, `[폐기]` 중 하나만 사용한다. - `[스케치]`는 방향성, 문제의식, 후보 범위, 미정 질문을 기록하는 컨셉 상태다. 구현 가능한 계획이 아니므로 구현 계획 생성 대상이 아니다. 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 ebe2b25..ba34f8a 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md @@ -111,7 +111,7 @@ Accept self-check completion only when `## Implementation Checklist` or its supp - Record the target id, opaque agent/model identity, execution class, runtime contract, catalog evidence, process identity, workspace identity, timestamps, result, and exact failure evidence. - Treat stderr as terminal diagnostic evidence. For JSONL, recognize generic terminal event fields such as error/fatal type or severity, rejected/failed status with an error code, explicit error flags, and a non-retrying `agent_end` whose last assistant message ends with `error` or `aborted`. - Determine liveness from PID/start-token/process-marker evidence and actual stream or native-session progress. Heartbeat mtime is never agent progress. For Codex JSONL, an unmatched `item.started` `command_execution` is an active tool interval: suspend the model-response silence timer until its matching `item.completed`, then restore normal stall detection. -- The dispatcher model-silence safety net is 70 seconds. Downstream provider runtimes should emit their bounded terminal before that deadline; do not extend the dispatcher budget per target to cover nested retries. +- The dispatcher model-silence safety net is 310 seconds. The dev Ornith provider's bounded response-stall terminal is 300 seconds, so the dispatcher remains slightly above it and observes that terminal instead of killing the caller first. Do not extend the dispatcher budget per target to cover nested retries. - Treat a confirmed provider transport terminal as the end of the current dispatch. Do not resume or automatically resend the same native session; an operator may start a fresh dispatch after the provider/runtime state is corrected. - When the selected target declares `session_stall_resume=true` and its JSONL emitted a runtime session id, terminate the silent process and invoke the catalog `resume_command` once for that exact same target and session with a continuation message. Do not inject a second continuation into the same stalled session; return to the existing bounded fresh-conversation retry and failover route. If the capability or runtime session id is absent, preserve workspace changes and logical locator evidence but retry with a fresh conversation. Never apply same-session continuation to provider transport terminals. - Never start a duplicate attempt while owned live evidence remains. @@ -135,6 +135,8 @@ python3 agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py Remove `--dry-run` to start execution. Add `--execution-catalog ` only to override the bundled default. Add `--task-group `, `--max-parallel `, or `--retry-blocked` only when requested by the workflow. +`--retry-blocked` is a forced fresh restart, never a continuation. Before using it, stop the dispatcher and confirm that no owned agent process is live. It preserves workspace edits, the active PLAN/CODE_REVIEW files, and failed run logs, but clears the scoped unfinished task's attempt counters, prior errors and blocker evidence, active locator/native-session linkage, recovery and generic failure budgets, persisted execution decisions, and route transition history. The next worker/reviewer must receive a newly generated session and an `initial` selector transition; it must not resume or inherit any earlier conversation. If owned live evidence remains, refuse the reset. + After an intentional catalog replacement invalidates a persisted incomplete worker decision, preview and accept it explicitly: ```bash 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 90f7284..d701d60 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 @@ -179,7 +179,10 @@ def validated_max_parallel(value: int) -> int: STREAM_HEARTBEAT_SECONDS = 30 -MODEL_RESPONSE_STALL_SECONDS = 70 +# The dev Ornith route allows five minutes for provider prefill/first output. +# Keep the dispatcher safety net slightly above that downstream terminal so it +# observes the provider result instead of terminating the caller first. +MODEL_RESPONSE_STALL_SECONDS = 310 RECOVERY_FAILURE_LIMIT = 10 GENERIC_FAILURE_LIMIT_PER_TARGET = 3 SELF_CHECK_UNCHECKED_RETRY_LIMIT = 10 @@ -1141,8 +1144,15 @@ class StateStore: self.save() return accepted - def mark_retry_failover(self, task_group: str | None = None, workspace: Path | None = None) -> None: + def reset_for_fresh_restart(self, task_group: str | None = None) -> None: + """Reset unfinished dispatcher state without resuming prior attempts. + + Operator-requested restart is a fresh execution boundary. Failed run + artifacts stay on disk as evidence, but no locator, native session, + selector transition, attempt number, or failure budget crosses it. + """ prefix = f"{task_group}/" if task_group else None + reset_tasks: set[str] = set() for task_name, value in self.data.get("tasks", {}).items(): if ( task_group is not None @@ -1150,45 +1160,58 @@ class StateStore: and not task_name.startswith(prefix) ): continue - if not value.get("blocked"): - continue - blocker_evidence = value.get("blocker_evidence") if isinstance(value.get("blocker_evidence"), dict) else {} - decisions = value.get("execution_decisions", {}) - worker_decision = decisions.get("worker") if isinstance(decisions, dict) else None - role = blocker_evidence.get("role") - failure_class = blocker_evidence.get("failure_class") - locator = blocker_evidence.get("locator") - selected = blocker_evidence.get("selected") - work_unit_id = blocker_evidence.get("work_unit_id") - qualified = ( - role == "worker" - and failure_class in QUALIFIED_FAILOVER_FAILURES - and isinstance(locator, str) - and locator.strip() - and isinstance(selected, dict) - and isinstance(work_unit_id, str) - and isinstance(worker_decision, dict) - and worker_decision.get("work_unit_id") == work_unit_id + unfinished = ( + not value.get("worker_done") + or bool(value.get("blocked")) + or bool(value.get("active_locator")) + or bool(value.get("retry_failover_pending")) ) - handoff_id = str(uuid.uuid4()) - retry_context = ({ - "role": role, - "failure_class": failure_class, - "locator": locator, - "selected": selected, - "work_unit_id": work_unit_id, - "handoff_id": handoff_id, - } if qualified else None) + if not unfinished: + continue + live, detail = external_active_is_live( + value, + expected_workspace=self.workspace, + expected_workspace_id=self.workspace_id, + expected_runs_root=self.runs, + ) + if live: + raise DispatcherTerminalStateError( + "fresh restart 전에 실행 중 agent를 중단해야 한다: " + f"task={task_name} detail={detail}" + ) value["blocked"] = None + value["blocker_evidence"] = None + value["active_stage"] = None + value["active_locator"] = None + value["active_started_at"] = None value["review_no_progress"] = 0 value["selfcheck_incomplete"] = 0 value["selfcheck_context_locator"] = None value["recovery_failures"] = {} value["stage_failure_budgets"] = {} - value["retry_failover_pending"] = qualified - value["retry_failover_context"] = retry_context - value["blocker_evidence"] = None + value["generic_failure_budgets"] = {} + value["retry_failover_pending"] = False + value["retry_failover_context"] = None + value["execution_decisions"] = {} + value["route_transition_history"] = [] + if not value.get("worker_done"): + value["worker_cli"] = None + value["worker_model"] = None + value["selfcheck_done"] = False + reset_tasks.add(task_name) + + counters = self.data.setdefault("attempt_counters", {}) + for key in list(counters): + task_name = key.split("|", 1)[0] + if task_name in reset_tasks: + del counters[key] + + claims = self.data.setdefault("write_claims", {}) + for task_name in reset_tasks: + claims.pop(task_name, None) + if reset_tasks: + self.write_claim_snapshot() self.save() @@ -6143,7 +6166,7 @@ async def dispatch_with_store( ) -> int: orchestration_scope = args.task_group or "__all__" if args.retry_blocked and not args.dry_run: - store.mark_retry_failover(args.task_group) + store.reset_for_fresh_restart(args.task_group) running: dict[str, asyncio.Task[str | None]] = {} last_wait: dict[str, str] = {} completed_tasks: dict[str, str] = {} @@ -6922,7 +6945,14 @@ def parse_args() -> argparse.Namespace: ), ) parser.add_argument("--dry-run", action="store_true", help="classify and print without launching CLIs") - parser.add_argument("--retry-blocked", action="store_true", help="clear dispatcher-local blocked state") + parser.add_argument( + "--retry-blocked", + action="store_true", + help=( + "fresh-restart unfinished tasks: clear attempt counters, prior " + "errors, locators/sessions, failure budgets, and selector history" + ), + ) parser.add_argument( "--accept-catalog-revision", action="store_true", 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 aee86e7..bc2de3f 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 @@ -356,7 +356,7 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase): decision["selected"], ) - def test_retry_blocked_marks_failover_without_quota_state(self): + def test_retry_blocked_resets_to_fresh_attempt_without_prior_context(self): with TemporaryDirectory() as tmp: root = Path(tmp) catalog = write_catalog(root) @@ -370,6 +370,8 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase): state = store.task_state(task) state.update( blocked="runtime failure", + active_stage=None, + active_locator=None, blocker_evidence={ "role": "worker", "failure_class": "provider-quota", @@ -377,13 +379,31 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase): "selected": decision["selected"], "work_unit_id": decision["work_unit_id"], }, + recovery_failures={"worker": 3}, + stage_failure_budgets={"unit|worker": {"count": 3}}, + generic_failure_budgets={"unit|worker|target": {"count": 2}}, + route_transition_history=[{"transition": "resume"}], + retry_failover_pending=True, + retry_failover_context={"locator": "/tmp/locator.json"}, ) + counter_key = f"{task.name}|{task.plan_hash}|worker" + store.data.setdefault("attempt_counters", {})[counter_key] = 5 store.save() - store.mark_retry_failover("group") + store.reset_for_fresh_restart("group") state = store.task_state(task) + counters = dict(store.data["attempt_counters"]) finally: store.close() - self.assertTrue(state["retry_failover_pending"]) + self.assertFalse(state["retry_failover_pending"]) + self.assertIsNone(state["retry_failover_context"]) + self.assertIsNone(state["blocker_evidence"]) + self.assertIsNone(state["blocked"]) + self.assertEqual(state["execution_decisions"], {}) + self.assertEqual(state["route_transition_history"], []) + self.assertEqual(state["recovery_failures"], {}) + self.assertEqual(state["stage_failure_budgets"], {}) + self.assertEqual(state["generic_failure_budgets"], {}) + self.assertNotIn(counter_key, counters) self.assertNotIn("quota_snapshot", state) self.assertNotIn("retry_quota_refresh_pending", state) diff --git a/agent-ops/skills/common/plan/SKILL.md b/agent-ops/skills/common/plan/SKILL.md index 1eb1fb6..9e799ed 100644 --- a/agent-ops/skills/common/plan/SKILL.md +++ b/agent-ops/skills/common/plan/SKILL.md @@ -195,7 +195,7 @@ Before choosing plan files or task directory names, apply the split decision pol Complete all items below before creating active plan/review files. Work through them in order; do not proceed to the next step until every checkbox is done. Keep the user request as the scope anchor and reconcile derived acceptance conditions before the split decision; do not create a separate routing summary. In `prepare-follow-up`, treat the reviewer's closed finding packet as the decision authority: repository reads validate its consistency and supply implementation mechanics, but do not reopen root cause or solution selection. If required evidence, root cause, or a selected fix is missing or contradicted, return `needs_evidence` to code-review so the reviewer corrects it in the same review pass; never pass investigation or alternatives to the worker. The only allowed file edits before writing plan/review files are local `agent-roadmap/current.md` creation or `.gitignore` block repair needed for roadmap routing. - [ ] **Resolve verification context** — because implementation plans include verification, consume supplied `verification_context` when present and confirm its source paths, commands, expected results, preconditions, constraints, gaps, and confidence still apply. On first pass, derive missing facts from repository manifests, scripts, workflows, domain rules, related tests, user-provided environment facts, and safe read-only probes. In `prepare-follow-up`, require the reviewer to have collected every fact needed for diagnosis and fix selection; derive only mechanical command/path details, and return `needs_evidence` rather than performing missing review analysis. Record which facts came from the handoff and which came from repository-native validation. A missing optional first-pass handoff is not a user-review blocker. -- [ ] **Keep the plan minimal** — choose the smallest change that satisfies the stated goal and required acceptance criteria. Reuse existing structure; exclude unrequested generalization, future-proofing, cleanup, and architectural expansion. +- [ ] **NO OVERENGINEERING** — Do not add anything beyond the user request and required behavior. - [ ] **Read all source files in full** — read every source file the change will touch, whole file. No partial reads. - [ ] **Preflight external verification** — when any required verification leaves the current checkout, including remote runner, field/bootstrap, external provider, Docker/code-server, emulator/device, or shared long-running runtime, confirm or derive a read-only preflight before writing final verification commands. Record runner, repo root/workdir, branch/HEAD/dirty state, source sync status, binary/artifact paths, command help/version output needed by the verification, config path, runtime identity, ports/process state, external hosts, and OS/arch assumptions. If the preflight shows stale artifacts, dirty/divergent checkout, wrong identity, missing command, closed ports, host OS mismatch, or unsynced source, add an explicit setup/sync/rebuild step or report the blocker. - [ ] **Read all test files in full** — read every test file that exercises the changed behavior, including files identified by the verification context and repository test layout. diff --git a/agent-ops/skills/common/update-roadmap/SKILL.md b/agent-ops/skills/common/update-roadmap/SKILL.md index 377e2e1..b0cdfbb 100644 --- a/agent-ops/skills/common/update-roadmap/SKILL.md +++ b/agent-ops/skills/common/update-roadmap/SKILL.md @@ -250,7 +250,7 @@ agent-roadmap/ | 작업 컨텍스트/TODO | 에이전트가 확정할 수 없는 결정 또는 조사/확인이 먼저 필요해 기능 Task로 확정하기 어렵다 | - 먼저 요청 내용의 규모를 판정한다. 배치 위치를 찾기 전에 `phase`, `milestone`, `epic`, `task`, `subtask`, `context` 중 가장 작은 충분한 단위를 고른다. -- Milestone에는 목표 달성에 필요한 최소 capability만 둔다. 완료 조건에 필수라는 근거가 없는 검증 도구, 자동화, 범용화는 범위 제외나 후속 Milestone으로 둔다. +- **NO OVERENGINEERING** — Do not add or retain anything beyond the user request and required behavior. - 요청이 방향성, 문제의식, 컨셉, 운영 원칙 수준이고 기능 Task나 실행 범위가 아직 부족하면 새 항목의 상태는 `[스케치]`로 둔다. - `[스케치]` Phase/Milestone을 만들 때는 `승격 조건`에 `[계획]`으로 전환하기 위해 필요한 정의, 결정, 경계, 후속 구현 Milestone 후보를 체크리스트로 남긴다. - 가장 작은 충분한 단위 원칙을 따른다. 애매하면 새 Phase나 새 Milestone으로 키우지 말고, 기존 Milestone의 Epic/Task에 넣을 수 있는지 먼저 확인한다.