diff --git a/README.md b/README.md index de7da4e66..0aeed8406 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,7 @@ Two examples already run at this load. The [deep-research engine](https://agentf | System prompt override | `system_prompt="..."` | | OpenCode per-run configuration | Preserves caller `OPENCODE_CONFIG_CONTENT` while applying the harness overlay | | OpenCode prompt compatibility | `AGENTFIELD_OPENCODE_INLINE_SYSTEM_PROMPT=1` enables the opt-in inline rollback | +| Provider-agnostic reasoning variants | `variant="high"` or a `#high` model suffix | | Multi-layer output recovery | Cosmetic repair → retry → full retry | #### Connector API (Fleet Management) diff --git a/docs/design/harness-v2-design.md b/docs/design/harness-v2-design.md index e87c72286..41e19afc9 100644 --- a/docs/design/harness-v2-design.md +++ b/docs/design/harness-v2-design.md @@ -98,6 +98,7 @@ fix = await app.harness( "Refactor to async/await", provider="codex", # Override default provider model="o3", # Override default model + variant="high", # Provider-specific reasoning effort max_turns=100, tools=["Read", "Write", "Edit", "Bash"], max_budget_usd=5.0, @@ -358,6 +359,7 @@ class HarnessConfig(BaseModel): # Provider selection: explicit > AGENTFIELD_HARNESS_PROVIDER > "aforge" provider: str = "aforge" # | "claude-code" | "codex" | "gemini" | "opencode" | "pi" | "omp" model: Optional[str] = None # None → the provider's own default + variant: Optional[str] = None # Explicit reasoning-effort variant # Execution limits max_turns: int = 30 diff --git a/docs/harness-providers.md b/docs/harness-providers.md index eff3495c4..fb0ec8d7f 100644 --- a/docs/harness-providers.md +++ b/docs/harness-providers.md @@ -198,7 +198,10 @@ result = await app.harness( ) ``` -An explicit `variant="high"` keyword wins over the suffix. Per provider: +An explicit `variant="high"` keyword wins over the suffix. In Python, +`variant` is also available on `HarnessConfig` and `HarnessRunner.run`; a +per-call `Agent.harness(..., variant=...)` value overrides the configured +default. Per provider: Pi and OMP accept the same OpenRouter model strings in every SDK, for example `openrouter/minimax/minimax-m2.7` or diff --git a/sdk/python/agentfield/agent.py b/sdk/python/agentfield/agent.py index eef169aef..8266510eb 100644 --- a/sdk/python/agentfield/agent.py +++ b/sdk/python/agentfield/agent.py @@ -3861,6 +3861,7 @@ async def harness( schema: Any = None, provider: Optional[str] = None, model: Optional[str] = None, + variant: Optional[str] = None, max_turns: Optional[int] = None, max_budget_usd: Optional[float] = None, tools: Optional[List[str]] = None, @@ -3885,6 +3886,8 @@ async def harness( "opencode", "grok", "pi", "omp"). Omit to use ``AGENTFIELD_HARNESS_PROVIDER`` when set, otherwise ``aforge``. model: Override model identifier. Empty uses the provider's own default. + variant: Provider-specific reasoning-effort variant. Wins over a + ``#variant`` suffix on the model when supported by the provider. max_turns: Maximum agent iterations. max_budget_usd: Cost cap in USD. tools: Allowed tools list. @@ -3917,6 +3920,7 @@ async def harness( schema=schema, provider=provider, model=model, + variant=variant, max_turns=max_turns, max_budget_usd=max_budget_usd, tools=tools, diff --git a/sdk/python/agentfield/harness/_runner.py b/sdk/python/agentfield/harness/_runner.py index 3f586bb2b..5e4a8d918 100644 --- a/sdk/python/agentfield/harness/_runner.py +++ b/sdk/python/agentfield/harness/_runner.py @@ -160,6 +160,7 @@ def _resolve_options( for field_name in [ "provider", "model", + "variant", "max_turns", "max_budget_usd", "max_retries", @@ -253,6 +254,7 @@ async def run( schema: Any = None, provider: Optional[str] = None, model: Optional[str] = None, + variant: Optional[str] = None, max_turns: Optional[int] = None, max_budget_usd: Optional[float] = None, tools: Optional[list[str]] = None, @@ -266,6 +268,7 @@ async def run( overrides = { "provider": provider, "model": model, + "variant": variant, "max_turns": max_turns, "max_budget_usd": max_budget_usd, "tools": tools, diff --git a/sdk/python/agentfield/types.py b/sdk/python/agentfield/types.py index 4cb9f1963..488473c1a 100644 --- a/sdk/python/agentfield/types.py +++ b/sdk/python/agentfield/types.py @@ -296,6 +296,13 @@ class HarnessConfig(BaseModel): "(aforge picks its own; claude-code uses sonnet)." ), ) + variant: Optional[str] = Field( + default=None, + description=( + "Provider-specific reasoning-effort variant (for example, " + '"high" or "minimal"). Wins over a #variant model suffix.' + ), + ) max_turns: int = Field(default=30, description="Maximum agent iterations.") max_budget_usd: Optional[float] = Field( default=None, description="Cost cap in USD." diff --git a/sdk/python/tests/test_harness_agent_wiring.py b/sdk/python/tests/test_harness_agent_wiring.py index bc3dfae84..c30e0eea0 100644 --- a/sdk/python/tests/test_harness_agent_wiring.py +++ b/sdk/python/tests/test_harness_agent_wiring.py @@ -104,6 +104,7 @@ async def test_harness_passes_all_options(self): "task", provider="codex", model="o3", + variant="high", max_turns=10, max_budget_usd=5.0, tools=["Read"], @@ -115,6 +116,7 @@ async def test_harness_passes_all_options(self): _, kwargs = mock_run.call_args assert kwargs["provider"] == "codex" assert kwargs["model"] == "o3" + assert kwargs["variant"] == "high" assert kwargs["max_turns"] == 10 assert kwargs["max_budget_usd"] == 5.0 assert kwargs["tools"] == ["Read"] diff --git a/sdk/python/tests/test_harness_runner.py b/sdk/python/tests/test_harness_runner.py index 033f428e6..adce2b751 100644 --- a/sdk/python/tests/test_harness_runner.py +++ b/sdk/python/tests/test_harness_runner.py @@ -403,6 +403,7 @@ async def test_run_resolves_harness_config_defaults_with_per_call_overrides(tmp_ config = SimpleNamespace( provider="codex", model="default-model", + variant="low", max_turns=30, max_budget_usd=1.5, tools=["Read", "Write"], @@ -422,6 +423,7 @@ async def test_run_resolves_harness_config_defaults_with_per_call_overrides(tmp_ await runner.run( "hello", model="override-model", + variant="max", max_turns=5, env={"OVERRIDE": "1"}, permission_mode="auto", @@ -430,6 +432,7 @@ async def test_run_resolves_harness_config_defaults_with_per_call_overrides(tmp_ assert provider.last_options is not None assert provider.last_options["provider"] == "codex" assert provider.last_options["model"] == "override-model" + assert provider.last_options["variant"] == "max" assert provider.last_options["max_turns"] == 5 assert provider.last_options["max_budget_usd"] == 1.5 assert provider.last_options["permission_mode"] == "auto" diff --git a/sdk/python/tests/test_harness_types.py b/sdk/python/tests/test_harness_types.py index a5e060424..fd26a4373 100644 --- a/sdk/python/tests/test_harness_types.py +++ b/sdk/python/tests/test_harness_types.py @@ -16,6 +16,7 @@ def test_harness_config_defaults(): assert cfg.provider == "codex" assert cfg.model is None + assert cfg.variant is None assert cfg.max_turns == 30 assert cfg.max_budget_usd is None assert cfg.max_retries == 3 @@ -35,6 +36,12 @@ def test_harness_config_defaults(): assert cfg.omp_bin == "omp" +def test_harness_config_accepts_explicit_variant(): + cfg = HarnessConfig(provider="opencode", variant="high") + + assert cfg.variant == "high" + + def test_build_provider_raises_for_unknown_provider(): cfg = HarnessConfig(provider="unknown-provider")