Skip to content

feat: support kilocode model calls through a Gym model server - #2319

Merged
ananthsub merged 1 commit into
NVIDIA-NeMo:mainfrom
ananthsub:ansubramania/kilocode-model-server
Aug 4, 2026
Merged

feat: support kilocode model calls through a Gym model server#2319
ananthsub merged 1 commit into
NVIDIA-NeMo:mainfrom
ananthsub:ansubramania/kilocode-model-server

Conversation

@ananthsub

@ananthsub ananthsub commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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.

@copy-pr-bot

copy-pr-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

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
ananthsub requested a review from Glorf August 4, 2026 16:21
@ananthsub ananthsub changed the title feat(kilocode): route model calls through a Gym model server feat: support kilocode model calls through a Gym model server Aug 4, 2026
@ananthsub
ananthsub force-pushed the ansubramania/kilocode-model-server branch from b08fb79 to d0cab5c Compare August 4, 2026 16:37
@ananthsub ananthsub added the r0.5.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge. label Aug 4, 2026
@ananthsub
ananthsub marked this pull request as ready for review August 4, 2026 16:47
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
ananthsub force-pushed the ansubramania/kilocode-model-server branch from d0cab5c to 57db66f Compare August 4, 2026 19:02
@copy-pr-bot

copy-pr-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ananthsub
ananthsub enabled auto-merge (squash) August 4, 2026 19:02
@ananthsub
ananthsub merged commit d4a22d6 into NVIDIA-NeMo:main Aug 4, 2026
8 checks passed
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
ananthsub deleted the ansubramania/kilocode-model-server branch August 4, 2026 19:40
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

r0.5.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants