sync: to agentic-framework v1.1.190

This commit is contained in:
toki 2026-08-08 11:50:25 +09:00
parent 9fcff70704
commit f44b00f480
10 changed files with 136 additions and 45 deletions

View file

@ -1 +1 @@
1.1.189
1.1.190

View file

@ -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/<task_group>` 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 <name>`, `--max-parallel <n>`, or `--retry-blocked` only when requested by the workflow.
Remove `--dry-run` to start execution. Add `--execution-catalog <path>` only to override the bundled default. Add `--task-group <name>`, `--max-parallel <n>`, 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.

View file

@ -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."

View file

@ -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"]}
}
}
}

View file

@ -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")

View file

@ -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)

View file

@ -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,

View file

@ -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__":

View file

@ -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__":

View file

@ -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__":