From a25533fd006b04ce320f9e39316cb8230a2fea0d Mon Sep 17 00:00:00 2001 From: toki Date: Sat, 8 Aug 2026 11:53:24 +0900 Subject: [PATCH] sync: agent-ops from agentic-framework v1.1.190 --- agent-ops/.version | 2 +- .../orchestrate-agent-task-loop/SKILL.md | 21 +++--- .../agents/openai.yaml | 4 +- .../assets/default-execution-catalog.json | 73 +++++++++++++++++++ .../scripts/dispatch.py | 4 +- .../scripts/execution_target_policy.py | 10 +-- .../scripts/select_execution_target.py | 26 ++++--- .../tests/test_dispatch.py | 15 +++- .../tests/test_dispatcher_observation.py | 2 +- .../tests/test_select_execution_target.py | 24 ++++-- 10 files changed, 136 insertions(+), 45 deletions(-) create mode 100644 agent-ops/skills/common/orchestrate-agent-task-loop/assets/default-execution-catalog.json diff --git a/agent-ops/.version b/agent-ops/.version index 257f522..d6f6af9 100644 --- a/agent-ops/.version +++ b/agent-ops/.version @@ -1 +1 @@ -1.1.189 +1.1.190 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 9d3ab55..b5481ab 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/SKILL.md @@ -1,6 +1,6 @@ --- name: orchestrate-agent-task-loop -description: Execute dependency-ready PLAN and CODE_REVIEW task loops with workspace write claims, a runtime-injected agent/model catalog, deterministic target failover, and persistent recovery state. +description: Execute dependency-ready PLAN and CODE_REVIEW task loops with workspace write claims, a bundled default agent/model catalog, runtime catalog overrides, deterministic target failover, and persistent recovery state. --- # Orchestrate Agent Task Loop @@ -21,7 +21,7 @@ Monitor the file-backed workflow under `agent-task/` and converge ready PLAN imp ## Inputs - `workspace`: trusted repository root containing `agent-task/`; defaults to the current directory. -- `execution_catalog`: required runtime agent/model catalog path, supplied with `--execution-catalog` or `AGENT_TASK_EXECUTION_CATALOG`. +- `execution_catalog`: optional runtime agent/model catalog override, supplied with `--execution-catalog` or `AGENT_TASK_EXECUTION_CATALOG`; otherwise use `assets/default-execution-catalog.json`. - `task_group`: optional `agent-task/` scope. - `dry_run`: inspect routes, dependencies, claims, and catalog validity without launching an agent. - `max_parallel`: workspace-wide active task-stage limit; defaults to `3`; `0` means unlimited. @@ -32,14 +32,14 @@ Monitor the file-backed workflow under `agent-task/` and converge ready PLAN imp ## Preconditions - Read the current plan and code-review contracts routed by `agent-ops/skills/common/router.md`. -- Obtain the execution catalog from the runtime or project layer. Common owns no default agent, model, provider, or route catalog. +- Use the bundled default catalog unless the runtime or project layer supplies an override. - Run `--dry-run` before the first live execution. - Never bypass the physical-workspace dispatcher lock. - Keep automatic approval inside the current workspace and the PLAN's declared write set. ## Runtime catalog contract -The catalog root contains exactly `schema_version`, `targets`, and `routes`. It must cover `worker` and `review`, and each stage must define every `local-G01` through `local-G10` and `cloud-G01` through `cloud-G10` route. +The bundled catalog is `assets/default-execution-catalog.json`. Catalog resolution order is explicit argument, `AGENT_TASK_EXECUTION_CATALOG`, then the bundled default. Every catalog root contains exactly `schema_version`, `targets`, and `routes`. It must cover `worker` and `review`, and each stage must define every `local-G01` through `local-G10` and `cloud-G01` through `cloud-G10` route. Each target has: @@ -57,17 +57,17 @@ Each route owns its ordered `candidates` plus optional `rule_id`, `policy_priori Before work starts, the dispatcher: -1. loads and validates the entire catalog; +1. resolves and validates the entire catalog; 2. verifies exact route coverage and every target reference; 3. verifies each target command is executable; 4. runs an optional target `preflight_command` for live execution; 5. records the catalog source and SHA-256 revision in the decision. -A persisted decision is valid only while the injected catalog revision and selected target snapshot still match. Catalog changes fail closed instead of silently changing an active work unit. +A persisted decision is valid only while the resolved catalog revision and selected target snapshot still match. Catalog changes fail closed instead of silently changing an active work unit. ## Selection and failover -- Initial execution selects the first candidate in the injected route. +- Initial execution selects the first candidate in the resolved route. - Resume pins the persisted target and route revision. - The dispatcher never queries quota before admission and never accepts a quota snapshot as selector input. - Classify actual terminal output after an attempt. `provider-quota`, `context-limit`, `model-unavailable`, `provider-stream-disconnect`, and `provider-connection` may advance to the next unused route candidate. @@ -121,18 +121,17 @@ Accept self-check completion only when `## Implementation Checklist` or its supp ```bash python3 agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py \ --workspace /absolute/repository \ - --execution-catalog /runtime/config/execution-catalog.json \ --dry-run ``` -Remove `--dry-run` to start execution. Add `--task-group `, `--max-parallel `, or `--retry-blocked` only when requested by the workflow. +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. Launch the live dispatcher as one persistent foreground process. Do not wrap it in an arbitrary timeout and do not start a second dispatcher after a normal tool yield. Wait on the same execution handle until an attention event or terminal exit. ## Completion checklist -- [ ] Catalog was injected, fully validated, preflighted, and revision-pinned. -- [ ] No fixed common agent/model/provider route or quota probe was used. +- [ ] The resolved catalog was fully validated, preflighted, and revision-pinned. +- [ ] No hidden route outside the resolved catalog or quota probe was used. - [ ] Runtime quota errors moved only to the next catalog candidate. - [ ] Dependencies, write claims, and workspace concurrency were enforced. - [ ] Required self-check and official review stages completed. diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/agents/openai.yaml b/agent-ops/skills/common/orchestrate-agent-task-loop/agents/openai.yaml index c53d2ab..f211deb 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/agents/openai.yaml +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Agent Task Loop Orchestrator" - short_description: "Orchestrate PLAN and review loops with an injected runtime catalog" - default_prompt: "Use $orchestrate-agent-task-loop to execute the active agent-task workflow." + short_description: "Orchestrate PLAN and review loops with a default runtime catalog" + default_prompt: "Use $orchestrate-agent-task-loop to execute the active agent-task workflow with the bundled catalog or a runtime override." diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/assets/default-execution-catalog.json b/agent-ops/skills/common/orchestrate-agent-task-loop/assets/default-execution-catalog.json new file mode 100644 index 0000000..3b1e34b --- /dev/null +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/assets/default-execution-catalog.json @@ -0,0 +1,73 @@ +{ + "schema_version": "1.0", + "targets": { + "codex-sol-xhigh": { + "agent": "codex", + "model": "gpt-5.6-sol", + "execution_class": "cloud_model", + "selfcheck_required": false, + "runtime": { + "command": [ + "codex", + "exec", + "--json", + "-C", + "{workspace}", + "-m", + "{model}", + "-c", + "model_reasoning_effort=\"xhigh\"", + "--dangerously-bypass-approvals-and-sandbox", + "{prompt}" + ], + "output_format": "jsonl" + } + } + }, + "routes": { + "worker": { + "local-G01": {"candidates": ["codex-sol-xhigh"]}, + "local-G02": {"candidates": ["codex-sol-xhigh"]}, + "local-G03": {"candidates": ["codex-sol-xhigh"]}, + "local-G04": {"candidates": ["codex-sol-xhigh"]}, + "local-G05": {"candidates": ["codex-sol-xhigh"]}, + "local-G06": {"candidates": ["codex-sol-xhigh"]}, + "local-G07": {"candidates": ["codex-sol-xhigh"]}, + "local-G08": {"candidates": ["codex-sol-xhigh"]}, + "local-G09": {"candidates": ["codex-sol-xhigh"]}, + "local-G10": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G01": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G02": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G03": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G04": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G05": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G06": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G07": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G08": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G09": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G10": {"candidates": ["codex-sol-xhigh"]} + }, + "review": { + "local-G01": {"candidates": ["codex-sol-xhigh"]}, + "local-G02": {"candidates": ["codex-sol-xhigh"]}, + "local-G03": {"candidates": ["codex-sol-xhigh"]}, + "local-G04": {"candidates": ["codex-sol-xhigh"]}, + "local-G05": {"candidates": ["codex-sol-xhigh"]}, + "local-G06": {"candidates": ["codex-sol-xhigh"]}, + "local-G07": {"candidates": ["codex-sol-xhigh"]}, + "local-G08": {"candidates": ["codex-sol-xhigh"]}, + "local-G09": {"candidates": ["codex-sol-xhigh"]}, + "local-G10": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G01": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G02": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G03": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G04": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G05": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G06": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G07": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G08": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G09": {"candidates": ["codex-sol-xhigh"]}, + "cloud-G10": {"candidates": ["codex-sol-xhigh"]} + } + } +} 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 1a921fb..e9b0443 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 @@ -6175,8 +6175,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--execution-catalog", help=( - "runtime agent/model catalog JSON; alternatively set " - "AGENT_TASK_EXECUTION_CATALOG" + "runtime agent/model catalog JSON override; alternatively set " + "AGENT_TASK_EXECUTION_CATALOG; defaults to the bundled catalog" ), ) parser.add_argument("--dry-run", action="store_true", help="classify and print without launching CLIs") diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/execution_target_policy.py b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/execution_target_policy.py index 0e0613a..8c80a82 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/execution_target_policy.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/execution_target_policy.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -"""Runtime-injected execution-target catalog and route policy. +"""Execution-target catalog and route policy. -This common module intentionally owns no agent or model catalog. A caller -supplies a JSON catalog at runtime; this module validates it and resolves one -ordered route without interpreting provider-specific identities. +The selector supplies either the bundled default catalog or a runtime override. +This module validates that catalog and resolves one ordered route without +interpreting provider-specific identities. """ from __future__ import annotations @@ -35,7 +35,7 @@ ALLOWED_TEMPLATE_FIELDS = { class CatalogError(ValueError): - """The injected execution catalog is missing or malformed.""" + """The resolved execution catalog is unreadable or malformed.""" @dataclass(frozen=True) 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 a5872b0..cb58afd 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 @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Select an execution target from a runtime-injected catalog. +"""Select an execution target from a bundled or runtime-overridden catalog. The selector performs no quota lookup. Every initial candidate is eligible; runtime failures such as ``provider-quota`` advance to the next catalog entry. @@ -19,6 +19,11 @@ from pathlib import Path SCHEMA_VERSION = "2.0" CATALOG_ENV = "AGENT_TASK_EXECUTION_CATALOG" +DEFAULT_CATALOG_PATH = ( + Path(__file__).resolve().parents[1] + / "assets" + / "default-execution-catalog.json" +) TIMEZONE_NAME = "UTC" _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}" @@ -63,12 +68,11 @@ class SelectorInputError(Exception): def resolve_catalog_path(value: str | Path | None = None) -> Path: raw = str(value) if value is not None else os.environ.get(CATALOG_ENV, "") - if not raw: - raise SelectorInputError( - "missing_execution_catalog", - f"inject the execution catalog with --catalog or {CATALOG_ENV}", - ) - return Path(raw).expanduser().resolve() + return ( + Path(raw).expanduser().resolve() + if raw + else DEFAULT_CATALOG_PATH.resolve() + ) def load_runtime_catalog(value: str | Path | None = None): @@ -261,7 +265,7 @@ def _catalog_matches_prior(catalog, prior: dict, decision) -> None: if evidence["revision"] != catalog.revision: raise SelectorInputError( code, - "the injected execution catalog changed after this work unit was selected", + "the resolved execution catalog changed after this work unit was selected", ) if evidence["route_id"] != decision.route_id: raise SelectorInputError(code, "persisted catalog route does not match the task route") @@ -271,10 +275,10 @@ def _validate_prior_candidate_identity(prior: dict, *, catalog, decision) -> Non code = "malformed_prior_decision" expected = [_candidate_snapshot(item, rank) for rank, item in enumerate(decision.candidates, 1)] if prior["candidates"] != expected: - raise SelectorInputError(code, "prior_decision candidates do not match the injected catalog route") + raise SelectorInputError(code, "prior_decision candidates do not match the resolved catalog route") selected = prior["selected"] if selected not in [{key: value for key, value in item.items() if key != "candidate_rank"} for item in expected]: - raise SelectorInputError(code, "prior_decision selected target is not in the injected route") + raise SelectorInputError(code, "prior_decision selected target is not in the resolved route") def _base_decision( @@ -406,7 +410,7 @@ def select_execution_target_for_route( ) next_index = selected_index + 1 if next_index >= len(route.candidates): - raise SelectorInputError("no_failover_candidate", "the injected route has no unused next target") + raise SelectorInputError("no_failover_candidate", "the resolved route has no unused next target") return _base_decision( catalog=catalog, route=route, 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 e2cd102..25e4f6a 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 @@ -313,17 +313,24 @@ class GenericDispatcherContractTests(unittest.TestCase): ) self.assertEqual(completed.returncode, 0, completed.stderr) - def test_dry_run_requires_catalog(self): + def test_dry_run_uses_bundled_catalog_by_default(self): with TemporaryDirectory() as tmp: + (Path(tmp) / "agent-task").mkdir() + env = { + key: value + for key, value in os.environ.items() + if key != "AGENT_TASK_EXECUTION_CATALOG" + } + env["XDG_STATE_HOME"] = str(Path(tmp) / "state") completed = subprocess.run( [sys.executable, str(SCRIPT), "--workspace", tmp, "--dry-run"], capture_output=True, text=True, - env={key: value for key, value in os.environ.items() if key != "AGENT_TASK_EXECUTION_CATALOG"}, + env=env, check=False, ) - self.assertEqual(completed.returncode, 2) - self.assertIn("missing_execution_catalog", completed.stderr) + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertNotIn("missing_execution_catalog", completed.stderr) if __name__ == "__main__": diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatcher_observation.py b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatcher_observation.py index 817b587..68861c2 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatcher_observation.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatcher_observation.py @@ -212,7 +212,7 @@ class SkillObservationContractTest(unittest.TestCase): self.assertIn("PID/start-token/process-marker evidence", skill) self.assertIn("never queries quota before admission", skill) self.assertIn("confirmed quota/rate-limit error advances directly", skill) - self.assertIn("Common owns no default agent, model, provider, or route catalog", skill) + self.assertIn("Use the bundled default catalog", skill) if __name__ == "__main__": 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 ea0f518..3e10063 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 @@ -71,12 +71,17 @@ def write_task( class SelectorTests(unittest.TestCase): - def test_catalog_must_be_injected(self): + def test_bundled_catalog_is_used_by_default(self): with TemporaryDirectory() as tmp, mock.patch.dict(os.environ, {}, clear=True): task = write_task(Path(tmp)) - with self.assertRaises(selector.SelectorInputError) as ctx: - selector.select_execution_target(task) - self.assertEqual(ctx.exception.code, "missing_execution_catalog") + result = selector.select_execution_target(task) + self.assertEqual(result["selected"]["target_id"], "codex-sol-xhigh") + self.assertEqual(result["selected"]["agent"], "codex") + self.assertEqual(result["selected"]["model"], "gpt-5.6-sol") + self.assertEqual( + result["catalog"]["source"], + str(selector.DEFAULT_CATALOG_PATH.resolve()), + ) def test_initial_decision_contains_catalog_evidence_and_no_quota(self): with TemporaryDirectory() as tmp: @@ -195,7 +200,7 @@ class SelectorTests(unittest.TestCase): selector.select_execution_target(missing, catalog_path=catalog) self.assertEqual(ctx.exception.code, "missing_milestone_task") - def test_cli_returns_structured_catalog_error(self): + def test_cli_uses_bundled_catalog_without_override(self): with TemporaryDirectory() as tmp: task = write_task(Path(tmp)) completed = subprocess.run( @@ -205,9 +210,12 @@ class SelectorTests(unittest.TestCase): env={key: value for key, value in os.environ.items() if key != selector.CATALOG_ENV}, check=False, ) - self.assertEqual(completed.returncode, 2) - self.assertEqual(completed.stdout, "") - self.assertEqual(json.loads(completed.stderr)["error"]["code"], "missing_execution_catalog") + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stderr, "") + self.assertEqual( + json.loads(completed.stdout)["selected"]["target_id"], + "codex-sol-xhigh", + ) if __name__ == "__main__":