sync: agent-ops from agentic-framework v1.1.190
This commit is contained in:
parent
e59cf544fd
commit
a25533fd00
10 changed files with 136 additions and 45 deletions
|
|
@ -1 +1 @@
|
|||
1.1.189
|
||||
1.1.190
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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"]}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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__":
|
||||
|
|
|
|||
Loading…
Reference in a new issue