Skip to content

fix(agent-challenge): supply LLM_MODEL to the evaluated agent - #63

Merged
echobt merged 1 commit into
mainfrom
fix/agent-llm-model-env
Jul 29, 2026
Merged

fix(agent-challenge): supply LLM_MODEL to the evaluated agent#63
echobt merged 1 commit into
mainfrom
fix/agent-llm-model-env

Conversation

@echobt

@echobt echobt commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Every Terminal-Bench evaluation scored 0/30 and every result.json carried "model_name": null. The packaged agent fails closed without a concrete model id:

agent run failed: A concrete model id is required: set LLM_MODEL
(the measured review harness supplies it under .rules). No default model is assumed.

This error never reached the database — it is written to exception.txt inside the trial dir, while the job log events are truncated at 16 KB. Diagnosed by running own_runner_backend by hand on the prod validator with the app's own environment.

Root cause — three independent layers

# Layer Behaviour
1 _terminal_bench_env never set LLM_MODEL; the platform only holds CHALLENGE_LLM_MODEL (settings.llm_model), which the agent never reads
2 sanitize_miner_env_for_job stripped a miner-supplied LLM_MODEL — the name is not token/key shaped
3 AGENT_ENV_ALLOWLIST admitted only LLM_COST_LIMIT and OPENROUTER_API_KEY

Empirically confirmed:

miner sends : ['BASEAGENT_MODEL', 'LLM_COST_LIMIT', 'LLM_MODEL', 'OPENROUTER_API_KEY']
survives    : ['LLM_COST_LIMIT', 'OPENROUTER_API_KEY']

OPENROUTER_API_KEY was reaching the agent correctly the whole time — only the model id was missing, so each trial died in ~5 s before any LLM call. It was structurally impossible for any submission to score.

Fix

  • _terminal_bench_env publishes settings.llm_model as LLM_MODEL, before the sanitized miner merge so a miner can override it.
  • LLM_MODEL joins MINER_ENV_PRODUCT_ALLOWLIST — a miner may bring their own key and their own model.
  • LLM_MODEL joins AGENT_ENV_ALLOWLIST so it survives filter_agent_env.
  • When an operator blanks llm_model, nothing is injected: the agent still fails loudly rather than running an unmeasured default.

Security invariants preserved

  • URL-shaped values are still rejected, so admitting the name opens no endpoint-injection hole.
  • Gateway / URL / host / proxy names remain excluded from both allowlists.
  • assert_no_untrusted_secret guards untouched; LLM_MODEL is not secret-shaped.
  • The ex-gateway test now asserts LLM_MODEL comes from settings and that no gateway base_url / token leaks — stronger than the previous blanket ban, which only existed because the gateway used to be its sole source.

Verification

  • 7 new tests: RED → GREEN.
  • Full agent-challenge suite: zero regressions — 26 pre-existing failures on main, 26 with this change, identical sets; passing 2286 → 2293.
  • On the prod validator, injecting LLM_MODEL at runtime moved the trial from dying in 5 s to running task containers for minutes (alexgshaw-cancel-async-tasks-...-tmux up 2 and 8 minutes).

Summary by CodeRabbit

  • New Features
    • Agent executions now receive the configured language model identifier through LLM_MODEL.
    • Miners can provide a valid model identifier to override the platform default.
  • Security
    • Model identifiers are allowed while gateway URLs, tokens, and URL-shaped values continue to be excluded.
  • Tests
    • Added coverage for model propagation, overrides, validation, isolation, and sensitive gateway-data protection.

Every Terminal-Bench evaluation scored 0 and every result.json carried
"model_name": null. The packaged agent fails closed without a concrete
model id:

  agent run failed: A concrete model id is required: set LLM_MODEL
  (the measured review harness supplies it under .rules).

Three independent layers dropped the model id, so no submission could
ever score:

1. the runner never set LLM_MODEL -- the platform only holds it as
   CHALLENGE_LLM_MODEL (settings.llm_model), which the agent never reads;
2. sanitize_miner_env_for_job stripped a miner-supplied LLM_MODEL because
   the name is not token/key shaped;
3. AGENT_ENV_ALLOWLIST admitted only LLM_COST_LIMIT and OPENROUTER_API_KEY.

The trial died in ~5s per task, before any LLM call, which is why the
whole leaderboard sat at zero while OPENROUTER_API_KEY was reaching the
agent correctly.

_terminal_bench_env now publishes settings.llm_model as LLM_MODEL, ahead
of the sanitized miner merge so a miner may override it; the model name
joins both the miner product allowlist and the agent sandbox allowlist.
A miner can therefore bring their own key and their own model. When an
operator blanks llm_model nothing is injected, so the agent still fails
loudly rather than running an unmeasured default.

Updated pins keep their intent: gateway/URL/host/proxy names stay
excluded, secret scrubbing still asserted, and the ex-gateway test now
asserts LLM_MODEL comes from settings and that no gateway base_url or
token leaks into the job env.

Verified: 7 new tests RED -> GREEN; full agent-challenge suite shows zero
regressions (26 pre-existing failures on main, 26 with this change,
identical sets; 2286 -> 2293 passing).
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

LLM model environment propagation

Layer / File(s) Summary
Allowlist and validation contracts
packages/challenges/agent-challenge/src/agent_challenge/evaluation/own_runner/isolation.py, packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py, packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py, packages/challenges/agent-challenge/tests/test_miner_env_lock.py, packages/challenges/agent-challenge/tests/test_own_runner_isolation.py
LLM_MODEL is accepted by environment allowlists while URL-shaped values remain rejected and secret detection remains unchanged.
Runner environment injection
packages/challenges/agent-challenge/src/agent_challenge/evaluation/runner.py, packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py
The runner supplies the platform model by default and uses a sanitized miner-provided model as an override.
Routing and regression coverage
packages/challenges/agent-challenge/tests/test_deepseek_via_gateway.py, packages/challenges/agent-challenge/tests/test_worker_security.py, packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py
Tests verify model propagation and confirm gateway URLs and scoped tokens are absent from the execution environment.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PlatformSettings
  participant terminal_bench_env
  participant sanitize_miner_env_for_job
  participant filter_agent_env
  participant AgentContainer
  PlatformSettings->>terminal_bench_env: provide settings.llm_model
  terminal_bench_env->>terminal_bench_env: set default LLM_MODEL
  terminal_bench_env->>sanitize_miner_env_for_job: validate miner LLM_MODEL
  sanitize_miner_env_for_job-->>terminal_bench_env: return accepted override
  terminal_bench_env->>filter_agent_env: forward LLM_MODEL
  filter_agent_env->>AgentContainer: retain LLM_MODEL
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: supplying LLM_MODEL to evaluated agents.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/agent-llm-model-env

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py (1)

48-52: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression coverage for a blank platform model.

The PR contract says blank settings.llm_model must leave LLM_MODEL unset, but these tests cover only the nonblank default and miner override. Add a test that sets the setting to "" and asserts the key is absent from _terminal_bench_env().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py`
around lines 48 - 52, Add a regression test alongside
test_runner_injects_platform_llm_model that temporarily sets settings.llm_model
to an empty string, calls _terminal_bench_env(), and asserts LLM_MODEL is absent
from the returned environment mapping. Restore the setting after the test using
the existing test framework’s fixture or cleanup mechanism.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py`:
- Around line 49-52: Remove LLM_MODEL from MINER_ENV_PRODUCT_ALLOWLIST and keep
it out of the secret validation path used by validate_miner_env() and
sanitize_miner_env_for_job(). Handle miner-provided model identifiers through a
separate narrowly scoped model-ID validator or allowlist, preserving rejection
of URL-shaped or endpoint-like values.

---

Nitpick comments:
In `@packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py`:
- Around line 48-52: Add a regression test alongside
test_runner_injects_platform_llm_model that temporarily sets settings.llm_model
to an empty string, calls _terminal_bench_env(), and asserts LLM_MODEL is absent
from the returned environment mapping. Restore the setting after the test using
the existing test framework’s fixture or cleanup mechanism.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a7c14913-ab5b-462b-9f39-0b3610eed45d

📥 Commits

Reviewing files that changed from the base of the PR and between 7e85c91 and f5a008b.

📒 Files selected for processing (8)
  • packages/challenges/agent-challenge/src/agent_challenge/evaluation/own_runner/isolation.py
  • packages/challenges/agent-challenge/src/agent_challenge/evaluation/runner.py
  • packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py
  • packages/challenges/agent-challenge/tests/test_deepseek_via_gateway.py
  • packages/challenges/agent-challenge/tests/test_llm_model_reaches_agent.py
  • packages/challenges/agent-challenge/tests/test_miner_env_lock.py
  • packages/challenges/agent-challenge/tests/test_own_runner_isolation.py
  • packages/challenges/agent-challenge/tests/test_worker_security.py

Comment on lines +49 to +52
# VAL-LLM-MODEL: a miner may bring their own key *and* their own model
# id. Not a URL/host/proxy name, and URL-shaped values stay rejected by
# ``looks_like_url_value``, so this opens no endpoint-injection hole.
"LLM_MODEL",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Locate and inspect miner_env.py"
fd -a 'miner_env\.py$' . | sed 's#^\./##'
file="$(fd 'miner_env\.py$' . | head -n1)"
if [ -n "${file:-}" ]; then
  echo "--- $file ($(wc -l < "$file") lines) ---"
  cat -n "$file"
fi

echo
echo "## Search for MINER_ENV_PRODUCT_ALLOWLIST and related validators"
rg -n --hidden --glob '*.py' 'MINER_ENV_PRODUCT_ALLOWLIST|is_token_or_key_env_name|looks_like_url_value|LLM_MODEL|model' packages/challenges/agent-challenge/src packages/challenges/agent-challenge || true

Repository: BaseIntelligence/base

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py"
echo "--- miner_env.py ---"
cat -n "$file"

echo
echo "--- relevant call sites ---"
sed -n '150,220p' packages/challenges/agent-challenge/src/agent_challenge/evaluation/own_runner/isolation.py | cat -n
sed -n '1628,1648p' packages/challenges/agent-challenge/src/agent_challenge/evaluation/runner.py | cat -n
sed -n '1988,2002p' packages/challenges/agent-challenge/src/agent_challenge/evaluation/runner.py | cat -n

echo
echo "--- behavioral probe of miner_env validation ---"
python3 - <<'PY'
import sys
sys.path.insert(0, 'packages/challenges/agent-challenge/src')
from agent_challenge.submissions.miner_env import (
    MINER_ENV_PRODUCT_ALLOWLIST,
    is_token_or_key_env_name,
    looks_like_url_value,
)

cases = {
    "LLM_MODEL": "x-ai/grok-4.5",
    "OPENROUTER_API_KEY": "sk-or-prod-secret",
    "LLM_MODEL_URL_HOST": "https://evil.internal.example/model",
    "LLM_MODEL_BEARER": "Bearer token=123",
    "X_VALID_KEY": "abc123",
    "PORT": "80",
}
for name, value in case := cases.items():
    print(name, {
        "is_allowed_token_or_key": is_token_or_key_env_name(name),
        "looks_like_url": looks_like_url_value(value),
        "upper_allowed": name.upper() in MINER_ENV_PRODUCT_ALLOWLIST,
        "value": value,
    })
PY

Repository: BaseIntelligence/base

Length of output: 13298


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "--- behavioral probe of miner_env validation ---"
python3 - <<'PY'
import sys
sys.path.insert(0, 'packages/challenges/agent-challenge/src')
from agent_challenge.submissions.miner_env import (
    MINER_ENV_PRODUCT_ALLOWLIST,
    is_token_or_key_env_name,
    looks_like_url_value,
)

cases = {
    "LLM_MODEL": "x-ai/grok-4.5",
    "LLM_MODEL_SECRET": "x-ai/grok-4.5",
    "OPENROUTER_API_KEY": "sk-or-prod-secret",
    "LLM_MODEL_URL_HOST": "https://evil.internal.example/model",
    "LLM_MODEL_BEARER": "Bearer token=123",
    "X_VALID_KEY": "abc123",
    "PORT": "80",
}
for name, value in cases.items():
    print(name, {
        "is_allowed_token_or_key": is_token_or_key_env_name(name),
        "looks_like_url": looks_like_url_value(value),
        "upper_allowed": name.upper() in MINER_ENV_PRODUCT_ALLOWLIST,
        "value": value,
    })
PY

echo
echo "--- submit/env route / API route env handling slices ---"
file="packages/challenges/agent-challenge/src/agent_challenge/api/routes.py"
sed -n '2820,2890p' "$file" | cat -n
sed -n '2898,2918p' "$file" | cat -n
rg -n "validate_miner_env|sanitize_miner_env_for_job|miner_env|SUBMIT.*ENV|env" "$file" -A 4 -B 4

Repository: BaseIntelligence/base

Length of output: 33243


Do not add LLM_MODEL to the secret allowlist.

MINER_ENV_PRODUCT_ALLOWLIST is the “API key / token / measured product secret” gate; LLM_MODEL is an OpenRouter model identifier, not a secret. Keeping it here lets miners submit and persist arbitrary model names through validate_miner_env()/sanitize_miner_env_for_job(); use a separate narrowly scoped model-id validator/allowlist instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py`
around lines 49 - 52, Remove LLM_MODEL from MINER_ENV_PRODUCT_ALLOWLIST and keep
it out of the secret validation path used by validate_miner_env() and
sanitize_miner_env_for_job(). Handle miner-provided model identifiers through a
separate narrowly scoped model-ID validator or allowlist, preserving rejection
of URL-shaped or endpoint-like values.

Source: Coding guidelines

@echobt
echobt merged commit 10f00b4 into main Jul 29, 2026
45 checks passed
@echobt
echobt deleted the fix/agent-llm-model-env branch July 29, 2026 19:10
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.

1 participant