Skip to content

Coerce AgentPolicyConfig fields to string (fixes #169) - #170

Open
Amresh-01 wants to merge 8 commits into
robocurve:mainfrom
Amresh-01:fix/agent-config-coercion
Open

Coerce AgentPolicyConfig fields to string (fixes #169)#170
Amresh-01 wants to merge 8 commits into
robocurve:mainfrom
Amresh-01:fix/agent-config-coercion

Conversation

@Amresh-01

@Amresh-01 Amresh-01 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #169

Description

This PR fixes #169 by strictly rejecting non-string parameters (model, base_url, api_key_env, effort, speed) in the LLMAgentPolicy constructor and raising a guided ConfigError.

This resolves the issue where unquoted CLI options parsed as booleans or integers (e.g. -P api_key_env=false or -P model=42) either recorded non-string values verbatim in the evaluation log or caused downstream tracebacks. Users are now guided to quote these values.

Changes

  1. Parameter Validation in LLMAgentPolicy:
    • Replaced parameter coercion logic with a validation loop that raises a guided ConfigError when non-strings are passed to model, base_url, api_key_env, effort, or speed.
  2. Robust Rerun-SDK Install Check:
    • Updated _RERUN_INSTALLED in test_rerun_sink.py to attempt to import rerun instead of find_spec, preventing false positives in environments with DLL load policies.
  3. Changelog:
    • Updated CHANGELOG.md entry under "Fixed" to reflect the strict validation change, wrapped at 80 columns.
  4. Tests:
    • Updated test_non_string_param_coercion to run parameterized checks for both truthy and falsy non-string parameters across all five fields, asserting that the proper guided ConfigError is raised.
    • Updated parameterization for test_falsy_api_key_env_does_not_send_the_openrouter_key_to_a_gateway in test_anthropic.py to only pass None and "" since non-strings are now caught and rejected at construction.

Verification

  • Synced the workspace package version locally.
  • Ran uv run ruff check plugins/inspect-robots-agent (all passed).
  • Ran uv run python -m pytest plugins/inspect-robots-agent/tests (396/396 passed).
  • Ran uv run python -m pytest tests/test_rerun_sink.py (55/55 passed).

@Amresh-01
Amresh-01 force-pushed the fix/agent-config-coercion branch 5 times, most recently from f97aa40 to d8029a4 Compare July 25, 2026 16:13

@jeqcho jeqcho left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is right, and I checked it end to end rather than by eye. On main, -P api_key_env=0 records 0 in the log and -P model=42 dies with AttributeError: 'int' object has no attribute 'partition' at _llm.py:111. On this branch every non-None case records as str and None passes through untouched. Your new test fails on main with that same AttributeError, so it's a real regression test and not just a shape assertion.

The str(x) if x else "" looks like a slip but it's load-bearing, and I'd put a line of comment on it. Coercing False to "False" would flip the truthiness that not api_key_env and api_key_env or _OPENROUTER_KEY depend on, and the lookup would go hunting for an env var literally named False. Falsy to "" keeps the OpenRouter guard and the ANTHROPIC_API_KEY fallback working, which I confirmed. Without a comment someone will simplify it to str(x) later and quietly break that.

Two things before this can go in.

The description doesn't match the diff. It says you corrected a build_toolset call signature inside bind() and added a missing state_labels() helper to Toolset in _tools.py. Neither is in the diff, and both already exist on main, from #110 and #134. The test counts don't line up either: the agent plugin suite is 264 tests, not 35, and there's no 398-test workspace run. The three files you actually touched are fine, so I think the body got pasted in from somewhere else. Please rewrite it to describe the real change, since it lands in merge history.

CHANGELOG entry under Unreleased, per CONTRIBUTING.md:131. This changes what the public LLMAgentPolicy constructor writes into the eval log, so it counts as user-visible.

Minor: test_policy_e2e.py:1010 sets env={"False": "test-key"}, which is never looked up, since False coerces to "" rather than "False". It reads as though the opposite were true. A plain dummy env would be clearer.

One more: the tests/test_rerun_sink.py change is unrelated to #169. It is correct, and I ran that file both with and without rerun installed, so I don't mind keeping it here. Just say so in the description.

@Amresh-01
Amresh-01 requested a review from jeqcho July 26, 2026 08:16
@Amresh-01

Copy link
Copy Markdown
Contributor Author

Hi @jeqcho, Please review this PR

@jeqcho

jeqcho commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

CHANGELOG entry looks right, and good call spelling out the falsy-to-"" reasoning in it.

Still need the description sorted before I merge. The build_toolset signature and state_labels() bullets, and the 35/398 test counts, none of that is in your diff and both of those helpers already exist on main. Once the body describes the four files you actually changed, this is good to go.

@Amresh-01

Copy link
Copy Markdown
Contributor Author

Hi @jeqcho,
The description now reflects the actual changes in the PR, including the LLMAgentPolicy coercion, falsy-to-"" behavior, regression test, CHANGELOG entry, and the unrelated rerun import robustness improvement.

Thanks for catching this. The remaining CI checks are now running.

@jeqcho

jeqcho commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Heads up: main's history was rewritten today to purge 3 GB of accidentally committed eval logs (#212), and this branch's history includes the old rewritten commits (it looks like main was merged into the branch at some point). Rebasing it is not mechanical because the original commits conflict with the current AgentPolicyConfig code, so those conflicts are yours to resolve. Suggested path:

git fetch upstream            # robocurve/inspect-robots
git rebase --onto upstream/main <your first commit>^ fix/agent-config-coercion
# resolve conflicts in policy.py and test_policy_e2e.py, then
git push --force-with-lease

Happy to review as soon as it is rebased.

@Amresh-01
Amresh-01 force-pushed the fix/agent-config-coercion branch from b2f68b4 to 51411e2 Compare July 29, 2026 10:26

@jeqcho jeqcho left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on — the coercion lands in the right place (before all the validation in LLMAgentPolicy.__init__, so validators see the coerced values), it covers all five unvalidated str | None params including speed, CI is green across every platform, and the new test passes locally for me too. A few things need attention before this is mergeable, though.

1. The falsy → "" special case is inconsistent with its own rationale (plugins/inspect-robots-agent/src/inspect_robots_agent/policy.py:258-271)

The comment justifies str(x) if x else "" by saying api_key_env=False coerced to "False" would look up an env var literally named False and bypass the key fallbacks. But the same logic applies to truthy values, and those are not protected: -P api_key_env=true still coerces to "True" and produces a lookup for an env var literally named True (verified — policy.config.api_key_env == "True"). So the special case only fixes half of the problem it describes, while introducing new inconsistencies:

  • -P model=0"" → falls into the "no model configured" unset path, while -P model=1"1". Same kind of typo, opposite outcomes, and the user's value is silently discarded in the falsy case.
  • -P effort=false now raises ConfigError: effort must be one of [...], got '' — the user typed false but the error shows '', which makes the message harder to act on than got 'False' would be.

I'd suggest one of two directions:

  • Uniform str(x) with no falsy special case, letting the existing validation and provider resolution fail loudly with the actual value in the message; or
  • Reject non-strings with a guided ConfigError, which is what this same constructor already does for exactly this failure mode elsewhere — see prior_learnings (policy.py:277-283, whose message even explains "the -P parser coerces unquoted values; pass -P 'prior_learnings="..."'") and the explicit isinstance(..., bool) guards on max_output_tokens and image_horizon. Issue #169 offered "narrowing at the constructor" or widening the annotation; the fail-fast spelling matches the repo's established style (cf. the #154 changelog entry) and I'd lean toward it — either way the truthy/falsy asymmetry should go.

2. The PR description is stale after the rebase

The body describes a build_toolset signature fix, a state_labels() helper in _tools.py, and timeline-transcript lint cleanups — none of these are in the current diff; they already exist on main. Please update the description to match what the PR actually changes now (coercion + test, rerun import check, CHANGELOG), and refresh the verification numbers if they were from the old branch state.

3. tests/test_rerun_sink.py change is unrelated to #169

The find_spec → try/import rerun switch for _RERUN_INSTALLED (tests/test_rerun_sink.py:34-39) is a reasonable robustness improvement on its own, but it has nothing to do with config coercion — it would be cleaner as its own small PR with its motivation stated there. If it stays, one note: pytest.importorskip("rerun") at the old line 1035 already skips on any import failure, so replacing it (tests/test_rerun_sink.py:1039-1042) isn't strictly needed — only the module-level flag change is load-bearing. Keeping the flag as the single source of truth is defensible, so this part is your call, but please say so explicitly in the description.

4. CHANGELOG (CHANGELOG.md:208)

  • The entry is one long unwrapped line; the surrounding entries wrap at roughly 80 columns — please match that.
  • The sentence "Falsy values are coerced to "" to preserve API key and OpenRouter fallback logic" documents the behavior questioned in item 1; it will need rewording once that's settled.

5. Test coverage (plugins/inspect-robots-agent/tests/test_policy_e2e.py:2524-2538)

test_non_string_param_coercion is a good direct check. Once item 1 is resolved, please also cover the currently-untested asymmetry cases: a truthy non-string api_key_env (e.g. True) and a falsy non-string model (e.g. 0), asserting whichever behavior you land on.

Happy to take another look after these — the core of the change is close, it's mainly the falsy special case and the housekeeping around the bundling that need tightening. Thanks again for the contribution! 🙏

@Amresh-01
Amresh-01 force-pushed the fix/agent-config-coercion branch from 5ee4371 to 67726a1 Compare July 30, 2026 02:04
@Amresh-01
Amresh-01 requested a review from jeqcho July 30, 2026 02:21
@Amresh-01

Copy link
Copy Markdown
Contributor Author

Hi @jeqcho,

I've addressed the latest review comments:

  • Switched to strict ConfigError validation for non-string config options.
  • Updated the tests to cover the requested cases.
  • Cleaned up the PR description and related changes.
  • Fixed lint issues and all CI checks are now passing.

Whenever you have time, I'd really appreciate another review. Thanks again for all the detailed feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AgentPolicyConfig records non-string -P values verbatim in the eval log

2 participants