Decision: Unify the qwen-code session-turns parameter name and semantics
Status: implemented Proposed: 2026-09-29 Implemented: 2026-09-29
Problem
The value that caps how long a qwen-code agent run may go on was exposed under three different names with a wrong semantic label:
| Layer | Old name | Actual behaviour |
|---|---|---|
CLI abx deploy |
--max-tool-calls (default 100, help: "tool-call limit") |
forwarded to --max-session-turns |
SDK Sandbox.deploy() / DeployModule.deploy_project() |
max_tool_calls (default 100, docstring: "number of tool calls") |
forwarded to --max-session-turns |
qwen-code template (template.yaml arg, commands.py signature) |
max_turns (defaults 100 / 50 — inconsistent) |
forwarded to --max-session-turns |
Upstream Qwen Code (official headless docs, verified 2026-09-29) defines
--max-session-turns (user/model/tool turns, overrun exits with code 53)
and --max-tool-calls (cumulative top-level tool calls, overrun exits
with code 55) as two distinct budgets. Exposing our session-turns
budget as --max-tool-calls therefore collides with a real, different
upstream flag and misleads anyone who knows the upstream CLI.
Decision
- Single public name:
max_session_turnseverywhere. - SDK keyword:
max_session_turns(Sandbox.deploy,DeployModule.deploy_project,DeployModule._build_qwen_command) - CLI option:
--max-session-turns - qwen-code template arg:
max_session_turns(default unified to100) - Semantics documented as "user/model/tool session turns; qwen-code
exits with code 53 when exceeded" (mapped to
turn_limit_exceeded). - Compatibility strategy: pre-release rename, no runtime aliases — consistent with ADR 2026-09-29 (remove-rename-notices) and ADR 2026-09-23 (overdesign cleanup): the project has no released users, so no deprecation shim is kept. Old names fail loudly:
--max-tool-callson the CLI → click usage error ("no such option"), locked bytest_max_tool_calls_option_rejected.max_tool_calls=SDK kwarg →TypeError, locked bytest_build_qwen_command_rejects_legacy_kwarg_name.--max-turns(the flag removed by the earlier task-180 fix; never a real upstream flag) must not reappear — locked bytest_build_qwen_command_omits_obsolete_flag. No unknown parameter is ever silently ignored.- Stable contract for the NL-create follow-up work (task 168): the
host adapter
run_qwen_code_headless()/_build_headless_command()inagent/qwen_code.pynow accepts keyword-onlymax_session_turns: int | None = None.None(the default) adds no flag, so currentabx createNL behaviour is byte-for-byte unchanged; future callers (clarification loop, diagnose, etc.) get the unified name on the same stable interface.
Alternatives considered
- Keep
max_tool_callsand also exposemax_session_turns— two names for one value plus a latent semantic trap against the real upstream--max-tool-calls. Rejected. - Deprecation aliases with
DeprecationWarning— the project is pre-release with no external users; alias machinery is over-design (same rationale as the rename-notices removal). Rejected. - Expose the upstream
--max-tool-callsbudget too — no current caller needs a tool-call budget; adding it is speculative scope. Rejected (can be added later under its own true name).
Dependencies
- Upstream qwen-code CLI
--max-session-turnsflag (verified against the official headless documentation; also live-checked bytest_build_qwen_command_flags_accepted_by_cliwhen a qwen binary is installed).
Test Strategy
tests/test_api/test_deploy.py— command building uses the new kwarg; legacy kwarg raisesTypeError; obsolete--max-turnsnever reappears.tests/test_cli/test_deploy_commands.py—--max-session-turnsis forwarded toSandbox.deploy(max_session_turns=...); legacy--max-tool-callsproduces a usage error.tests/test_agent/test_qwen_code.py—_build_headless_commandadds no flag by default and forwards--max-session-turnswhen given;run_qwen_code_headlessforwards the budget to the subprocess argv.
Files changed
src/agent_sandbox/api/deploy.py,src/agent_sandbox/api/sandbox.py— SDK rename + docstring semanticssrc/agent_sandbox/cli/commands/deploy.py—--max-session-turnsoption, help, examplessrc/agent_sandbox/agent/qwen_code.py— optionalmax_session_turnson the host adapterexamples/templates/qwen-code/template.yaml,commands.py,examples/templates/README.md— template arg rename + default unified to 100tests/test_api/test_deploy.py,tests/test_cli/test_deploy_commands.py,tests/test_agent/test_qwen_code.pydocs/{en,zh}/reference/api-reference.md,docs/{en,zh}/reference/cli-reference.md,docs/{en,zh}/design/cli-design.md,docs/{en,zh}/guide/deploy-and-build.mdCHANGELOG.md
Acceptance criteria
- ✅
max_session_turns/--max-session-turnsis the only public name across CLI, SDK, template, tests, and docs (grep for old names returns nothing outside lock-tests/changelog) - ✅ Old names fail loudly; nothing is silently ignored
- ✅
run_qwen_code_headlessdefault behaviour unchanged (no flag without the new kwarg) - ✅ Targeted pytest, ruff, mypy pass; CLI evidence golden files unaffected (no deploy help evidence exists)