Two agent bugs found while calibrating the new benchmark tasks. Both invalidated calibration runs, and both would fail real milestones too.
1. Strict chat templates rejected turns that start with text
Symptom: 10 of Devstral Small 2's runs failed with a 500 from llama.cpp: raise_exception('After the optional system message, conversation roles must alternate user/assistant…').
Cause: the OpenAI renderer made one message per event. When a model writes some text and then calls a tool in the same turn, that turn was sent as a text message followed by a tool-call message, two assistant messages in a row. Mistral's template rejects that. I captured the failing request from a real run; its tail was tool → assistant (text) → assistant (tool call) → tool.
Fix:render.py folds consecutive user or assistant messages into one: text is joined, and tool_calls are combined. That's the proper OpenAI shape for a turn; the Anthropic renderer already merges this way.
Verified live: with the fix, 6 real Devstral runs of fix-bug and long-output gave no 500s. Before it, 3 of 5 fix-bug runs and 5 of 5 long-output runs hit it.
2. A fixed 120 s request timeout
Symptom: 4 calibration runs "escalated" with The read operation timed out. Three were qwen3.8-27b, including both of its spec-parser runs, and one was mimo-v2.6-9b.
Cause:LiteLLMClient hardcoded timeout_s=120 (the passthrough client used 300). A dense 27B on the 4090 writing a whole module in one turn takes longer.
Fix: a new setting, llm.request_timeout_s, for both clients, default 600 s. It's a safety net for hung connections, not a work budget: max_output_tokens and max_turns bound the work. It's added to nisse-dev.example.yaml and the Tuning Guide.
Tests
test_render.py: text plus parallel calls from one turn give one assistant message. No transcript renders two user or two assistant messages in a row. The compaction test's expectation is updated, since a summary next to a user message now shares it.
test_config.py: the setting and its default, rejection of bad values, and that it reaches both clients.
CI steps run locally: ruff check, ruff format --check, and pytest -m "not integration" (407 passed).
Deferred
The benchmark still counts setup failures (500s, timeouts, 429s) against the model. Reporting them separately is a follow-up.
Two agent bugs found while calibrating the new benchmark tasks. Both invalidated calibration runs, and both would fail real milestones too.
## 1. Strict chat templates rejected turns that start with text
**Symptom:** 10 of Devstral Small 2's runs failed with a 500 from llama.cpp: `raise_exception('After the optional system message, conversation roles must alternate user/assistant…')`.
**Cause:** the OpenAI renderer made one message per event. When a model writes some text and then calls a tool in the same turn, that turn was sent as a text message followed by a tool-call message, two assistant messages in a row. Mistral's template rejects that. I captured the failing request from a real run; its tail was `tool → assistant (text) → assistant (tool call) → tool`.
**Fix:** `render.py` folds consecutive user or assistant messages into one: text is joined, and `tool_calls` are combined. That's the proper OpenAI shape for a turn; the Anthropic renderer already merges this way.
**Verified live:** with the fix, 6 real Devstral runs of `fix-bug` and `long-output` gave no 500s. Before it, 3 of 5 `fix-bug` runs and 5 of 5 `long-output` runs hit it.
## 2. A fixed 120 s request timeout
**Symptom:** 4 calibration runs "escalated" with `The read operation timed out`. Three were qwen3.8-27b, including both of its `spec-parser` runs, and one was mimo-v2.6-9b.
**Cause:** `LiteLLMClient` hardcoded `timeout_s=120` (the passthrough client used 300). A dense 27B on the 4090 writing a whole module in one turn takes longer.
**Fix:** a new setting, `llm.request_timeout_s`, for both clients, **default 600 s**. It's a safety net for hung connections, not a work budget: `max_output_tokens` and `max_turns` bound the work. It's added to `nisse-dev.example.yaml` and the Tuning Guide.
## Tests
- **`test_render.py`:** text plus parallel calls from one turn give one assistant message. No transcript renders two user or two assistant messages in a row. The compaction test's expectation is updated, since a summary next to a user message now shares it.
- **`test_config.py`:** the setting and its default, rejection of bad values, and that it reaches both clients.
CI steps run locally: `ruff check`, `ruff format --check`, and `pytest -m "not integration"` (407 passed).
## Deferred
The benchmark still counts setup failures (500s, timeouts, 429s) against the model. Reporting them separately is a follow-up.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two setup failures found while calibrating the new benchmark tasks,
both of which would also fail real milestones.
Devstral's chat template (Mistral) rejected any turn in which the model
wrote some text before a tool call: the OpenAI renderer made one message
per event, so the turn became a text message and a tool-call message in
a row, and llama.cpp answered 500. The renderer now folds consecutive
user or assistant messages into one, which is the OpenAI format for a
turn anyway. Checked against the live server: the captured failing
request no longer occurs, over six real runs.
LiteLLMClient gave up on any request after a fixed 120 s. A dense 27B
model writing a whole module in one turn takes longer, so the executor
escalated work that was going well. llm.request_timeout_s now sets it
for both clients, default 600.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two agent bugs found while calibrating the new benchmark tasks. Both invalidated calibration runs, and both would fail real milestones too.
1. Strict chat templates rejected turns that start with text
Symptom: 10 of Devstral Small 2's runs failed with a 500 from llama.cpp:
raise_exception('After the optional system message, conversation roles must alternate user/assistant…').Cause: the OpenAI renderer made one message per event. When a model writes some text and then calls a tool in the same turn, that turn was sent as a text message followed by a tool-call message, two assistant messages in a row. Mistral's template rejects that. I captured the failing request from a real run; its tail was
tool → assistant (text) → assistant (tool call) → tool.Fix:
render.pyfolds consecutive user or assistant messages into one: text is joined, andtool_callsare combined. That's the proper OpenAI shape for a turn; the Anthropic renderer already merges this way.Verified live: with the fix, 6 real Devstral runs of
fix-bugandlong-outputgave no 500s. Before it, 3 of 5fix-bugruns and 5 of 5long-outputruns hit it.2. A fixed 120 s request timeout
Symptom: 4 calibration runs "escalated" with
The read operation timed out. Three were qwen3.8-27b, including both of itsspec-parserruns, and one was mimo-v2.6-9b.Cause:
LiteLLMClienthardcodedtimeout_s=120(the passthrough client used 300). A dense 27B on the 4090 writing a whole module in one turn takes longer.Fix: a new setting,
llm.request_timeout_s, for both clients, default 600 s. It's a safety net for hung connections, not a work budget:max_output_tokensandmax_turnsbound the work. It's added tonisse-dev.example.yamland the Tuning Guide.Tests
test_render.py: text plus parallel calls from one turn give one assistant message. No transcript renders two user or two assistant messages in a row. The compaction test's expectation is updated, since a summary next to a user message now shares it.test_config.py: the setting and its default, rejection of bad values, and that it reaches both clients.CI steps run locally:
ruff check,ruff format --check, andpytest -m "not integration"(407 passed).Deferred
The benchmark still counts setup failures (500s, timeouts, 429s) against the model. Reporting them separately is a follow-up.
🤖 Generated with Claude Code