diff --git a/CHANGELOG.md b/CHANGELOG.md index c785b1c..8a949a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to `geny-executor` are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/). +## [2.0.1] — 2026-05-18 + +Patch release. Fixes a crash when a manifest names ``"subagent_type"`` +as the Stage 12 orchestrator strategy. + +### Fixed + +- ``SubagentTypeOrchestrator.__init__`` now accepts ``registry=None`` + and falls back to an empty :class:`SubagentTypeRegistry`. The + ``StrategySlot`` machinery zero-arg-constructs the orchestrator + during ``PipelineMutator.restore``, before + ``Pipeline._wire_subagent_orchestrator`` has had a chance to bind + the real registry. In 2.0.0 that crashed with + ``__init__() missing 1 required positional argument: 'registry'``; + in 2.0.1 the temporary empty instance is harmless — every delegate + request lands as ``"unknown_agent_type"`` until the wire step + replaces the orchestrator with one bound to the host's registry. + +### Migration + +None. Hosts that already pass ``subagent_registry=`` to +``Pipeline.from_manifest_async`` keep their existing behaviour; the +fix only matters during the brief window between strategy restore +and post-restore wiring. + ## [2.0.0] — 2026-05-17 **Major release.** The LLM client layer is generalised to support every diff --git a/pyproject.toml b/pyproject.toml index e2a6ec1..3d85b9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "geny-executor" -version = "2.0.0" +version = "2.0.1" description = "Harness-engineered agent pipeline library with 21-stage dual-abstraction architecture, built on the Anthropic API" readme = "README.md" license = "MIT" diff --git a/src/geny_executor/__init__.py b/src/geny_executor/__init__.py index 07a30a1..7df39d9 100644 --- a/src/geny_executor/__init__.py +++ b/src/geny_executor/__init__.py @@ -95,7 +95,7 @@ ProviderDrivenStrategy, ) -__version__ = "2.0.0" +__version__ = "2.0.1" __all__ = [ # Core diff --git a/src/geny_executor/stages/s12_agent/subagent_type.py b/src/geny_executor/stages/s12_agent/subagent_type.py index a37536a..8da95c8 100644 --- a/src/geny_executor/stages/s12_agent/subagent_type.py +++ b/src/geny_executor/stages/s12_agent/subagent_type.py @@ -183,8 +183,16 @@ class SubagentTypeOrchestrator(AgentOrchestrator): the same way. """ - def __init__(self, registry: SubagentTypeRegistry): - self._registry = registry + def __init__(self, registry: Optional[SubagentTypeRegistry] = None): + # ``registry`` is logically required, but accepting ``None`` lets + # zero-arg construction work — which the ``StrategySlot`` machinery + # uses while restoring a manifest that names ``"subagent_type"`` as + # the orchestrator. The pipeline immediately replaces this instance + # with one bound to the real registry via + # ``Pipeline._wire_subagent_orchestrator``; until that runs, the + # orchestrator behaves as if no descriptors are registered (every + # delegate request lands as an "unknown_agent_type" failure). + self._registry = registry if registry is not None else SubagentTypeRegistry() @property def name(self) -> str: diff --git a/tests/unit/test_subagent_orchestrator_zero_arg.py b/tests/unit/test_subagent_orchestrator_zero_arg.py new file mode 100644 index 0000000..a767022 --- /dev/null +++ b/tests/unit/test_subagent_orchestrator_zero_arg.py @@ -0,0 +1,55 @@ +"""Regression test for the 2.0.1 fix: SubagentTypeOrchestrator must +accept zero-arg construction so the StrategySlot restore path that +runs *before* Pipeline._wire_subagent_orchestrator doesn't crash. +""" + +from __future__ import annotations + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "src")) + +import pytest + +from geny_executor.core.state import PipelineState +from geny_executor.stages.s12_agent.subagent_type import ( + SubagentTypeDescriptor, + SubagentTypeOrchestrator, + SubagentTypeRegistry, +) + + +def test_orchestrator_accepts_zero_arg() -> None: + """The StrategySlot machinery instantiates the strategy with no + constructor args during PipelineMutator.restore. 2.0.0 raised + TypeError here; 2.0.1 returns a usable instance with an empty + registry.""" + orch = SubagentTypeOrchestrator() + assert orch._registry is not None + assert len(orch._registry) == 0 + + +def test_orchestrator_explicit_registry_unchanged() -> None: + reg = SubagentTypeRegistry() + reg.register(SubagentTypeDescriptor( + agent_type="x", factory=lambda ctx: None, description="x" + )) + orch = SubagentTypeOrchestrator(reg) + assert orch._registry is reg + assert len(orch._registry) == 1 + + +@pytest.mark.asyncio +async def test_zero_arg_orchestrate_treats_unknown_agent_as_structured_failure() -> None: + """Before _wire_subagent_orchestrator runs, the orchestrator has an + empty registry. Any delegate request must land as a structured + failure, not a crash.""" + orch = SubagentTypeOrchestrator() + state = PipelineState(session_id="s") + state.delegate_requests = [{"agent_type": "missing", "task": "t"}] + result = await orch.orchestrate(state) + assert result.delegated is True + assert len(result.sub_results) == 1 + assert result.sub_results[0]["success"] is False + assert "unknown_agent_type" in (result.sub_results[0].get("error") or "")