fix(effort): per-role floor for --effort resolution + developer→xhigh
resolve_agent_effort returned '' for all agents in prod because empty ORCH_AGENT_EFFORT_*= env vars clobber pydantic class-defaults, leaving no non-empty floor to fall back to -> --effort never reached the Claude CLI. Add a level-4 per-role floor in resolve_agent_effort (src/agents/launcher.py): _agent_effort_floor reads the declared class-default of agent_effort_<agent> (model_fields[...].default), which a present-but-empty env cannot override. Floor applies only when levels 1-3 are empty and BEFORE validation, so a typo (non-empty) still drops to '' (never-break ORCH-41) and explicit env/override still wins (priority preserved). config.py: agent_effort_developer high->xhigh (single source of truth; floor follows automatically). Refs: ORCH-081 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -158,12 +158,50 @@ def resolve_agent_model(agent: str, project_id: str = None) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _agent_effort_floor(agent: str) -> str:
|
||||
"""ORCH-081 (ORCH-52h): per-role non-empty floor for --effort resolution.
|
||||
|
||||
Returns the DECLARED class-default of the ``agent_effort_<agent>`` field on
|
||||
Settings (e.g. developer -> ``xhigh``, tester/deployer -> ``medium``, the rest
|
||||
-> ``high``). This is the value pydantic WOULD have used were it not clobbered
|
||||
by a spurious empty env var (``ORCH_AGENT_EFFORT_<ROLE>=``): the class-default
|
||||
is fixed in the class body and a present-but-empty env value cannot override it,
|
||||
so it is a robust floor even when the host ``.env`` zeroes every effort var.
|
||||
|
||||
config.py is the single source of truth: upgrading developer to ``xhigh`` there
|
||||
automatically raises the floor here — no second map to keep in sync (ADR-001).
|
||||
|
||||
Unknown agent (a name outside the 6 roles) has no ``agent_effort_<agent>``
|
||||
field; we degrade to the class-default of ``agent_effort_default`` (``high``),
|
||||
a safe non-empty floor. Never raises.
|
||||
"""
|
||||
fields = type(settings).model_fields
|
||||
for key in (f"agent_effort_{agent}", "agent_effort_default"):
|
||||
field = fields.get(key)
|
||||
if field is not None and field.default:
|
||||
return field.default
|
||||
return ""
|
||||
|
||||
|
||||
def resolve_agent_effort(agent: str, project_id: str = None) -> str:
|
||||
"""ORCH-41: resolve the --effort level for an agent (optionally per-project).
|
||||
|
||||
Same priority as resolve_agent_model. The resolved value is validated against
|
||||
VALID_EFFORTS; an invalid value is logged and dropped (returns "") so a typo
|
||||
in env/projects_json can never pass a bad flag to the CLI.
|
||||
Same priority as resolve_agent_model, with one extra level below the global
|
||||
default (ORCH-081 / ADR-001):
|
||||
1. project-override (projects_json.agent_efforts[agent])
|
||||
2. per-agent env (settings.agent_effort_<agent>)
|
||||
3. global default (settings.agent_effort_default)
|
||||
4. per-role FLOOR (class-default of agent_effort_<agent>) — NEW
|
||||
|
||||
The floor only kicks in when levels 1-3 are all empty (the prod bug: a present
|
||||
but empty ``ORCH_AGENT_EFFORT_*=`` clobbers every default to ''), guaranteeing
|
||||
a non-empty target effort for the 6 known roles regardless of host .env state.
|
||||
|
||||
The floor is applied BEFORE validation and ONLY to an empty resolve, so it
|
||||
never masks a typo: an explicit invalid value (e.g. ``turbo``) is non-empty,
|
||||
skips the floor, and is logged + dropped to "" exactly as in ORCH-41 (the
|
||||
resolved value is validated against VALID_EFFORTS; an invalid value can never
|
||||
pass a bad flag to the CLI). Never raises.
|
||||
"""
|
||||
value = _resolve_agent_attr(
|
||||
agent, project_id,
|
||||
@@ -171,6 +209,11 @@ def resolve_agent_effort(agent: str, project_id: str = None) -> str:
|
||||
env_attr_prefix="agent_effort_",
|
||||
default_attr="agent_effort_default",
|
||||
)
|
||||
if not value:
|
||||
# Levels 1-3 all empty (typically a prod .env with empty ORCH_AGENT_EFFORT_*):
|
||||
# fall through to the per-role floor (class-default). Applied before
|
||||
# validation but only here, so a typo (non-empty) never reaches this branch.
|
||||
value = _agent_effort_floor(agent)
|
||||
if value and value not in VALID_EFFORTS:
|
||||
logger.warning(
|
||||
f"Invalid effort '{value}' for agent '{agent}' "
|
||||
|
||||
@@ -97,13 +97,15 @@ class Settings(BaseSettings):
|
||||
agent_model_deployer: str = ""
|
||||
|
||||
# ORCH-41: per-agent effort / reasoning level: low|medium|high|xhigh|max.
|
||||
# Empty -> agent_effort_default. Same resolution order as model. Default split:
|
||||
# thinking agents (analyst/architect/developer/reviewer) -> high; mechanical
|
||||
# agents (tester/deployer) -> medium.
|
||||
# Empty -> agent_effort_default. Same resolution order as model. Default split
|
||||
# (ORCH-081/ORCH-52h): thinking agents (analyst/architect/reviewer) -> high;
|
||||
# developer -> xhigh (coding/agentic role, Opus 4.8 canon); mechanical agents
|
||||
# (tester/deployer) -> medium. These class-defaults are ALSO the per-role floor
|
||||
# used by resolve_agent_effort when the env is empty (single source of truth).
|
||||
agent_effort_default: str = "high"
|
||||
agent_effort_analyst: str = "high"
|
||||
agent_effort_architect: str = "high"
|
||||
agent_effort_developer: str = "high"
|
||||
agent_effort_developer: str = "xhigh"
|
||||
agent_effort_reviewer: str = "high"
|
||||
agent_effort_tester: str = "medium"
|
||||
agent_effort_deployer: str = "medium"
|
||||
|
||||
Reference in New Issue
Block a user