Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions devops_bench/agents/cli/claude_code/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2026 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Claude Code CLI agent harness, named to distinguish it from the Claude model.

The harness driver lives in :mod:`.agent` and the stream-json parser in
:mod:`.parsing`. Importing this package self-registers the agent under the
``"claude"`` key via ``@AGENTS.register``.
"""

from __future__ import annotations

from devops_bench.agents.cli.claude_code.agent import ClaudeCodeAgent
from devops_bench.agents.cli.claude_code.parsing import parse_stream_json

__all__ = ["ClaudeCodeAgent", "parse_stream_json"]
398 changes: 398 additions & 0 deletions devops_bench/agents/cli/claude_code/agent.py

Large diffs are not rendered by default.

361 changes: 361 additions & 0 deletions devops_bench/agents/cli/claude_code/parsing.py

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions devops_bench/evalharness/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
# registry, with no edit here.
_BUILTIN_AGENT_MODULES: tuple[str, ...] = (
"devops_bench.agents.cli.gemini_cli",
"devops_bench.agents.cli.claude_code",
"devops_bench.agents.cli.openclaw",
"devops_bench.agents.cli.antigravity",
"devops_bench.agents.api.agent",
Expand All @@ -79,6 +80,7 @@
# Aliases normalized to canonical agent keys before registry lookup.
_AGENT_TYPE_ALIASES: dict[str, str] = {
"gemini-cli": "gemini",
"claude-code": "claude",
}

# Default agent type when neither --agent-type nor BENCH_AGENT_TYPE is set.
Expand Down Expand Up @@ -123,6 +125,17 @@ def _ensure_builtin_agents_registered() -> None:
_log.debug("optional agent module %s not importable: %s", module, exc)


def _canonical_agent_type(agent_type: str) -> str:
"""Normalize an agent-type alias to its canonical registry key.

The single source of truth for both registry lookup and result recording,
so an arm selected via a friendly alias (``claude-code`` / ``gemini-cli``)
aggregates under the same ``harness`` / ``setup_id`` as the canonical key
instead of splitting into a second dashboard setup.
"""
return _AGENT_TYPE_ALIASES.get(agent_type, agent_type)


class DefaultEvalHarness(Harness):
"""Standard harness wiring every component into one pipeline.

Expand Down Expand Up @@ -235,7 +248,7 @@ def resolve_agent(self, agent_type: str) -> Any:
canonical key.
"""
_ensure_builtin_agents_registered()
key = _AGENT_TYPE_ALIASES.get(agent_type, agent_type)
key = _canonical_agent_type(agent_type)
agent_cls = AGENTS.get(key)
if agent_cls is None:
raise NotRegisteredError(AGENTS.name, key, AGENTS.keys())
Expand Down Expand Up @@ -674,14 +687,18 @@ def _write_run_artifacts(self, run_dir: Path, detailed_results: list[dict[str, A
augmentation = derive_augmentation(
{"use_mcp": self.use_mcp, "skills": list(self._granted_skill_paths)}
)
model = self._agent_config.model or self._agent_config.provider or self.agent_type
# Record the canonical harness key so an arm selected via a friendly
# alias (e.g. ``claude-code`` / ``gemini-cli``) aggregates with the
# canonical key rather than splitting into a second dashboard setup.
harness = _canonical_agent_type(self.agent_type)
model = self._agent_config.model or self._agent_config.provider or harness
manifest = Manifest(
schema_version=SCHEMA_VERSION,
run_id=run_dir.name,
t=datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
setup_id=results_setup_id(model, self.agent_type, augmentation),
setup_id=results_setup_id(model, harness, augmentation),
model=model,
harness=self.agent_type,
harness=harness,
augmentation=augmentation,
)
rows = build_rows(detailed_results, manifest)
Expand Down
6 changes: 3 additions & 3 deletions docs/components/model_providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ API-key variable, or keyless auth — those belong to the provider contract in

## Supported providers and models

Every harness — the `api` runner and the `gemini`/`openclaw` CLIs — resolves
`AGENT_PROVIDER` through one shared contract
Every harness — the `api` runner and the `gemini`/`claude`/`openclaw` CLIs —
resolves `AGENT_PROVIDER` through one shared contract
(`devops_bench/core/model_providers.py`). A provider resolves to an *adapter
family* (which `LLMClient` the `api` harness builds), a *backend* hint
(genai/vertex/bedrock), the *openclaw wire-provider*, and the *API-key env
Expand Down Expand Up @@ -96,7 +96,7 @@ final fallback.

> [!NOTE]
> **All harnesses share one provider contract.** The `api` runner and the
> `gemini`/`openclaw` CLIs all resolve `AGENT_PROVIDER` through
> `gemini`/`claude`/`openclaw` CLIs all resolve `AGENT_PROVIDER` through
> `devops_bench/core/model_providers.py`. The `api` harness uses it to pick the
> adapter family and backend for `get_model()`; the CLI harnesses use it to route
> `AGENT_API_KEY` onto the binary's provider-specific env var(s) (e.g. `google` →
Expand Down
7 changes: 4 additions & 3 deletions docs/how-to/add-a-model-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ absent.

`AGENT_PROVIDER` resolves through the shared contract in
`devops_bench/core/model_providers.py` — the one place every harness (the `api`
runner and the `gemini`/`openclaw` CLIs) reads. If your provider needs aliases, a
distinct backend, a non-default API-key env var, or keyless (ADC-style) auth, add
a `ProviderSpec` row to `_SPECS` and its alias(es) to `_ALIASES` there:
runner and the `gemini`/`claude`/`openclaw` CLIs) reads. If your provider needs
aliases, a distinct backend, a non-default API-key env var, or keyless
(ADC-style) auth, add a `ProviderSpec` row to `_SPECS` and its alias(es) to
`_ALIASES` there:

```python
_SPECS["your-key"] = ProviderSpec(
Expand Down
Loading