feat: support kilocode model calls through a Gym model server - #2319
Merged
ananthsub merged 1 commit intoAug 4, 2026
Merged
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
ananthsub
force-pushed
the
ansubramania/kilocode-model-server
branch
from
August 4, 2026 16:37
b08fb79 to
d0cab5c
Compare
ananthsub
marked this pull request as ready for review
August 4, 2026 16:47
Glorf
approved these changes
Aug 4, 2026
The kilocode agent called its model provider directly, so its requests and responses never reached Gym: model calls went uncaptured, the provider block had to be rewritten per backend, and there was no path to token IDs or logprobs. It was the last CLI agent without a model server. Add `model_server` to KiloCodeAgentConfig. When set, the agent writes a `nemo` provider into kilo.json pointed at that server and passes `-m nemo/<model>`, so one config runs against vLLM, OpenAI, or an inference provider by swapping `--model-type`. `_write_kilo_config` becomes build-then-write; it used to return early on an empty `kilo_config`, which would have left a model-server-only run with no provider at all. During `/run` the base URL carries the `/ng-rollout/<id>` capture prefix, so captured calls are attributable to the rollout that made them. Three fields describe the served model. They are agent config rather than `kilo_config` keys because the config merge is struct-mode, so a config using `_inherit_from` cannot add keys under `models`: - `context_window` and `max_output_tokens` set kilo's `limit`. The defaults are sized for a 32k-window model server. Kilo's system prompt and tool definitions run to ~10k tokens, so a larger output budget pushes `prompt + max_tokens` past `max_model_len`; vLLM rejects that with a 400 that the Gym model server turns into an empty completion with `finish_reason: length` rather than an error, so the run yields no assistant message and scores zero. The event parser now warns when it sees that shape. - `reasoning_field` sets `interleaved.field`. Kilo turns interleaved reasoning off for custom openai-compatible providers unless the field is named, so without it the reasoning channel is dropped. Setting `model_server: null` keeps the previous behaviour of calling a provider declared in `kilo_config` directly. Verified against @kilocode/cli 7.4.15 with a Gym model server fronting vLLM. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub
force-pushed
the
ansubramania/kilocode-model-server
branch
from
August 4, 2026 19:02
d0cab5c to
57db66f
Compare
ananthsub
enabled auto-merge (squash)
August 4, 2026 19:02
ananthsub
added a commit
that referenced
this pull request
Aug 4, 2026
…319)` into `r0.5.0` (#2326) beep boop [🤖]: Hi @ananthsub 👋, we've cherry picked #2319 into for you! 🚀 Please review and approve this cherry pick by your convenience! Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com> Signed-off-by: NeMo Bot <nemo-bot@nvidia.com> Co-authored-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub
added a commit
to ananthsub/Gym
that referenced
this pull request
Aug 6, 2026
Terminology: say "Agent server" consistently for the NeMo Gym component, in prose, headings, and table headers. Correct how a wrapper is structured. The harness runs from the Agent server's responses(); run() is the episode orchestrator that seeds the session, calls responses(), and verifies. Every agent server follows this split (simple_agent, opencode_agent, claude_code_agent, codex_agent, pi_agent). Drop the built-in vs external framing. The agent servers that wrap a third-party harness are also in this repo, so that split was not a real distinction. Group by who implements the agent instead: a Gym-native implementation, or a third-party harness. Rename "Agent Frameworks" to "External Frameworks", since that table holds both agent frameworks (LangGraph, Stirrup) and environment frameworks (Aviary, Verifiers). Use "in" rather than "against" for the harness-to-environment relation. "Against" frames the agent as the sole driver, which does not hold if the environment orchestrates agents for multi-agent workloads. Add the material from the closed PR NVIDIA-NeMo#2390 that this page lacked: a selection guide, the resources_server/model_server and sandbox config shapes, the per-request policy endpoint override, the skills caveat, and the commands to start servers and collect rollouts. Rebased onto current main, which changes three facts documented earlier: - mini_swe_agent and mini_swe_agent_2 accept policy_base_url and policy_api_key on /run (NVIDIA-NeMo#2166). Other agent servers allow extra keys, so they accept the fields and ignore them; the page says so. - kilocode_agent now routes through a Gym model server (NVIDIA-NeMo#2319), so all six CLI agent servers take a model_server ref. - harbor_agent can execute trials in any NeMo Gym sandbox provider (NVIDIA-NeMo#2296), not only Singularity. Verified with `make docs-check` (fern check, 0 errors). Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
OlegSudakov
pushed a commit
to OlegSudakov/Gym
that referenced
this pull request
Aug 7, 2026
…-NeMo#2319) ## Summary The kilocode agent called its model provider directly, so its requests and responses never reached Gym. Model calls went uncaptured, the provider block had to be rewritten per backend, and there was no path to token IDs or logprobs. This adds `model_server` to `KiloCodeAgentConfig`. When set, the agent writes a `nemo` provider into `kilo.json` pointed at that server and passes `-m nemo/<model>`, so one config runs against vLLM, OpenAI, or an inference provider by swapping `--model-type`. Setting `model_server: null` keeps the previous behaviour of calling a provider declared in `kilo_config`. ## What changed - `model_server` wiring: `_write_kilo_config` becomes build-then-write. It used to return early when `kilo_config` was empty, which would have left a model-server-only run with no provider at all. `_build_command` and `_env` now prefer the resolved URL, so a config carrying both a model server and `openai_base_url` does not point the subprocess environment at the provider the `nemo` provider is meant to replace. - Per-rollout capture: During `/run` the base URL carries the `/ng-rollout/<id>` prefix, using the base class's `url_path_for_run` and `resolve_model_base_url` rather than a local reimplementation. Captured model calls are attributable to the rollout that made them. ## On the model limits `context_window` and `max_output_tokens` set kilo's `limit`. The defaults are sized for a 32k-window model server rather than copied from `opencode_agent`. Kilo's system prompt and tool definitions run to ~10k tokens, so a large output budget pushes `prompt + max_tokens` past `max_model_len`. vLLM rejects that with a 400, which the Gym model server converts into an empty completion with `finish_reason: length` rather than an error (`vllm_model/app.py`, `is_out_of_context_length`). The run then produces no assistant message and scores zero, with nothing in the CLI's output to say why. Two related notes: - There is no truncating default to guard against. An unlisted model gets `limit.output: 0`, and kilo's `min(limit.output, 32000) || 32000` falls back to 32000. The hazard is a budget that is too *large*, not too small. - `reasoning_field` sets `interleaved.field`. Kilo turns interleaved reasoning off for custom openai-compatible providers unless the field is named (its built-in default only applies it to model ids containing "deepseek"), so without this the reasoning channel is dropped. It defaults to `reasoning_content`, which is what Gym model servers write; vLLM >= 0.16 also sends `reasoning`, which is why it is configurable rather than hardcoded. Kilo splits `-m` on the first `/` only (`let [A, ...L] = H.split("/")`), so a slashed model name such as `nemo/Qwen/Qwen2.5-7B-Instruct` resolves to the model `Qwen/Qwen2.5-7B-Instruct` under the provider `nemo`. Verified against the shipped binary and live. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub
added a commit
to ananthsub/Gym
that referenced
this pull request
Sep 1, 2026
Terminology: say "Agent server" consistently for the NeMo Gym component, in prose, headings, and table headers. Correct how a wrapper is structured. The harness runs from the Agent server's responses(); run() is the episode orchestrator that seeds the session, calls responses(), and verifies. Every agent server follows this split (simple_agent, opencode_agent, claude_code_agent, codex_agent, pi_agent). Drop the built-in vs external framing. The agent servers that wrap a third-party harness are also in this repo, so that split was not a real distinction. Group by who implements the agent instead: a Gym-native implementation, or a third-party harness. Rename "Agent Frameworks" to "External Frameworks", since that table holds both agent frameworks (LangGraph, Stirrup) and environment frameworks (Aviary, Verifiers). Use "in" rather than "against" for the harness-to-environment relation. "Against" frames the agent as the sole driver, which does not hold if the environment orchestrates agents for multi-agent workloads. Add the material from the closed PR NVIDIA-NeMo#2390 that this page lacked: a selection guide, the resources_server/model_server and sandbox config shapes, the per-request policy endpoint override, the skills caveat, and the commands to start servers and collect rollouts. Rebased onto current main, which changes three facts documented earlier: - mini_swe_agent and mini_swe_agent_2 accept policy_base_url and policy_api_key on /run (NVIDIA-NeMo#2166). Other agent servers allow extra keys, so they accept the fields and ignore them; the page says so. - kilocode_agent now routes through a Gym model server (NVIDIA-NeMo#2319), so all six CLI agent servers take a model_server ref. - harbor_agent can execute trials in any NeMo Gym sandbox provider (NVIDIA-NeMo#2296), not only Singularity. Verified with `make docs-check` (fern check, 0 errors). Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The kilocode agent called its model provider directly, so its requests and responses never reached Gym. Model calls went uncaptured, the provider block had to be rewritten per backend, and there was no path to token IDs or logprobs.
This adds
model_servertoKiloCodeAgentConfig. When set, the agent writes anemoprovider intokilo.jsonpointed at that server and passes-m nemo/<model>, so one config runs against vLLM, OpenAI, or an inference provider by swapping--model-type. Settingmodel_server: nullkeeps the previous behaviour of calling a provider declared inkilo_config.What changed
model_serverwiring:_write_kilo_configbecomes build-then-write. It used to return early whenkilo_configwas empty, which would have left a model-server-only run with no provider at all._build_commandand_envnow prefer the resolved URL, so a config carrying both a model server andopenai_base_urldoes not point the subprocess environment at the provider thenemoprovider is meant to replace./runthe base URL carries the/ng-rollout/<id>prefix, using the base class'surl_path_for_runandresolve_model_base_urlrather than a local reimplementation. Captured model calls are attributable to the rollout that made them.On the model limits
context_windowandmax_output_tokensset kilo'slimit. The defaults are sized for a 32k-window model server rather than copied fromopencode_agent.Kilo's system prompt and tool definitions run to ~10k tokens, so a large output budget pushes
prompt + max_tokenspastmax_model_len. vLLM rejects that with a 400, which the Gym model server converts into an empty completion withfinish_reason: lengthrather than an error (vllm_model/app.py,is_out_of_context_length). The run then produces no assistant message and scores zero, with nothing in the CLI's output to say why.Two related notes:
limit.output: 0, and kilo'smin(limit.output, 32000) || 32000falls back to 32000. The hazard is a budget that is too large, not too small.reasoning_fieldsetsinterleaved.field. Kilo turns interleaved reasoning off for custom openai-compatible providers unless the field is named (its built-in default only applies it to model ids containing "deepseek"), so without this the reasoning channel is dropped. It defaults toreasoning_content, which is what Gym model servers write; vLLM >= 0.16 also sendsreasoning, which is why it is configurable rather than hardcoded.Kilo splits
-mon the first/only (let [A, ...L] = H.split("/")), so a slashed model name such asnemo/Qwen/Qwen2.5-7B-Instructresolves to the modelQwen/Qwen2.5-7B-Instructunder the providernemo. Verified against the shipped binary and live.