fix(orchestrator): 기본 병렬 실행 수를 3으로 제한한다
This commit is contained in:
parent
2cd1e506bd
commit
a45475817a
3 changed files with 33 additions and 27 deletions
|
|
@ -53,7 +53,7 @@ Treat Korean text inside code spans or fenced examples as exact runtime or file-
|
|||
- `workspace`: Trusted repository root containing `agent-task/` (optional; defaults to the current directory).
|
||||
- `task_group`: Name of a specific `agent-task/<task_group>` to run (optional).
|
||||
- `dry_run`: Inspect state, routes, and dependencies without starting a CLI (optional).
|
||||
- `max_parallel`: Non-negative integer cap on unique active task-stage attempts across the physical workspace; `0` is unlimited (default). State that `--task-group` does not narrow occupancy, adopted external attempts count, internal helper coroutines do not count separately, and the flag must be supplied again on restart.
|
||||
- `max_parallel`: Non-negative integer cap on unique active task-stage attempts across the physical workspace. Omission defaults to `3`; explicit `0` is unlimited. `--task-group` does not narrow occupancy, adopted external attempts count, internal helper coroutines do not count separately, and an override must be supplied again after restart.
|
||||
- `retry_blocked`: Explicitly retry the same PLAN blocked by a previous dispatcher run in non-dry-run mode (optional). With `task_group`, reset only that group's blockers and 10-attempt counters while preserving other group state.
|
||||
|
||||
## Preconditions
|
||||
|
|
@ -80,7 +80,7 @@ Treat Korean text inside code spans or fenced examples as exact runtime or file-
|
|||
|
||||
Concurrency limits:
|
||||
|
||||
- Global physical-workspace limit: `max_parallel=0` is unlimited; a positive value caps unique active task-stage attempts and is not narrowed by `task_group`. The cap applies across worker, self-check, review, and verified external-active attempts in the same physical workspace.
|
||||
- Global physical-workspace limit: omitting `max_parallel` caps execution at `3`; explicit `max_parallel=0` is unlimited. A positive value caps unique active task-stage attempts and is not narrowed by `task_group`. The cap applies across worker, self-check, review, and verified external-active attempts in the same physical workspace.
|
||||
- Pi `ornith:35b`: 3.
|
||||
- agy: 1.
|
||||
- Official Codex review: no separate review-only limit; subject to the global
|
||||
|
|
@ -183,7 +183,7 @@ When recovering a KST-night `local-G07`–`local-G08` Laguna locator or a termin
|
|||
- Never infer an implicit dependency from numeric order alone.
|
||||
|
||||
2. **Run the dispatcher.**
|
||||
- Run all active tasks:
|
||||
- Run all active tasks with the default physical-workspace cap of `3`:
|
||||
|
||||
```bash
|
||||
python3 agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py
|
||||
|
|
@ -201,6 +201,12 @@ When recovering a KST-night `local-G07`–`local-G08` Laguna locator or a termin
|
|||
python3 agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py --max-parallel 2
|
||||
```
|
||||
|
||||
- Explicitly disable the cap:
|
||||
|
||||
```bash
|
||||
python3 agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py --max-parallel 0
|
||||
```
|
||||
|
||||
- Preview classification without launching CLIs under the same cap:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ WORK_LOG_NAME = "WORK_LOG.md"
|
|||
WORK_LOG_ARCHIVE_RE = re.compile(r"^work_log_(\d+)\.log$")
|
||||
AGENT_PROCESS_MARKER_ENV = "IOP_AGENT_TASK_EXECUTION_ID"
|
||||
KST = timezone(timedelta(hours=9), name="KST")
|
||||
DEFAULT_MAX_PARALLEL = 3
|
||||
|
||||
|
||||
def validated_max_parallel(value: int) -> int:
|
||||
|
|
@ -116,6 +117,8 @@ def validated_max_parallel(value: int) -> int:
|
|||
f"--max-parallel must be >= 0, got {value}"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
STREAM_HEARTBEAT_SECONDS = 30
|
||||
PI_MODEL_RESPONSE_STALL_SECONDS = 3 * 60
|
||||
PI_SESSION_SCHEMA_VERSION = 3
|
||||
|
|
@ -5900,7 +5903,9 @@ async def dispatch_with_store(
|
|||
legacy_recoveries: dict[str, LegacyPromotionRecovery] = {}
|
||||
live_external_processes: dict[str, str] = {}
|
||||
capacity_waiting: set[str] = set()
|
||||
max_parallel = validated_max_parallel(getattr(args, "max_parallel", 0))
|
||||
max_parallel = validated_max_parallel(
|
||||
getattr(args, "max_parallel", DEFAULT_MAX_PARALLEL)
|
||||
)
|
||||
|
||||
while True:
|
||||
if task_cache is None:
|
||||
|
|
@ -6731,11 +6736,11 @@ def parse_args() -> argparse.Namespace:
|
|||
parser.add_argument(
|
||||
"--max-parallel",
|
||||
type=int,
|
||||
default=0,
|
||||
default=DEFAULT_MAX_PARALLEL,
|
||||
metavar="MAX_PARALLEL",
|
||||
help=(
|
||||
"physical-workspace global cap on unique active task-stage "
|
||||
"attempts; 0 is unlimited (default)"
|
||||
f"attempts; default is {DEFAULT_MAX_PARALLEL}; 0 is unlimited"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
|
|
@ -6749,7 +6754,9 @@ def parse_args() -> argparse.Namespace:
|
|||
def main() -> int:
|
||||
args = parse_args()
|
||||
try:
|
||||
validated_max_parallel(getattr(args, "max_parallel", 0))
|
||||
validated_max_parallel(
|
||||
getattr(args, "max_parallel", DEFAULT_MAX_PARALLEL)
|
||||
)
|
||||
except ValueError as exc:
|
||||
print(f"dispatcher error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
|
|
|||
|
|
@ -9614,7 +9614,12 @@ class ThroughputQuotaBatchTest(unittest.TestCase):
|
|||
mock.patch.object(dispatch, "implementation_review_errors", return_value=[]),
|
||||
mock.patch.object(selector, "probe_candidate_quota", side_effect=mock_probe),
|
||||
):
|
||||
args = SimpleNamespace(task_group="route", retry_blocked=False, dry_run=False)
|
||||
args = SimpleNamespace(
|
||||
task_group="route",
|
||||
retry_blocked=False,
|
||||
dry_run=False,
|
||||
max_parallel=0,
|
||||
)
|
||||
exit_code = await asyncio.wait_for(
|
||||
dispatch.dispatch_with_store(args, workspace, store),
|
||||
timeout=5.0,
|
||||
|
|
@ -11446,27 +11451,15 @@ class ParallelLimitSchedulingTest(unittest.IsolatedAsyncioTestCase):
|
|||
tasks.append(task)
|
||||
return workspace, tasks
|
||||
|
||||
def test_unlimited_default_selects_all_disjoint_ready(self):
|
||||
"""Default max_parallel=0 admits every disjoint ready task."""
|
||||
workspace, tasks = self._make_workspace()
|
||||
ready = [
|
||||
(tasks[0], "review"),
|
||||
(tasks[1], "review"),
|
||||
(tasks[2], "worker"),
|
||||
(tasks[3], "selfcheck"),
|
||||
]
|
||||
store = dispatch.StateStore(workspace)
|
||||
try:
|
||||
selected, deferred, _ = dispatch.select_dispatch_candidates(
|
||||
store, ready, persist=False, available_slots=None,
|
||||
)
|
||||
finally:
|
||||
store.close()
|
||||
self.assertEqual(selected, ready)
|
||||
self.assertEqual(deferred, [])
|
||||
def test_omitted_cli_value_defaults_to_three(self):
|
||||
"""Omitting --max-parallel applies the workspace-global default of three."""
|
||||
with mock.patch("sys.argv", ["dispatch.py"]):
|
||||
args = dispatch.parse_args()
|
||||
self.assertEqual(dispatch.DEFAULT_MAX_PARALLEL, 3)
|
||||
self.assertEqual(args.max_parallel, dispatch.DEFAULT_MAX_PARALLEL)
|
||||
|
||||
def test_explicit_zero_selects_all_disjoint_ready(self):
|
||||
"""Explicit max_parallel=0 admits every disjoint ready task."""
|
||||
"""Explicit max_parallel=0 preserves the unlimited override."""
|
||||
workspace, tasks = self._make_workspace()
|
||||
ready = [
|
||||
(tasks[0], "review"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue