-
Notifications
You must be signed in to change notification settings - Fork 654
Add a built-in Cline coding-agent harness #2431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
arafatkatze
wants to merge
8
commits into
PrimeIntellect-ai:main
Choose a base branch
from
arafatkatze:cline-harness
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
99e4322
feat: add built-in Cline harness
arafatkatze 0dace07
test: cover Cline in agentic e2e matrix
arafatkatze 58f6efc
docs: document Cline harness usage and limits
arafatkatze 87dcc41
test: verify Cline harness launch plumbing
arafatkatze 940b05a
test: rely on v1 Cline e2e coverage
arafatkatze 4268422
fix(cline): preserve native tool defaults
arafatkatze 6650884
feat(cline): use native ACP harness
arafatkatze d539ef0
fix(cline): address harness review feedback
arafatkatze File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| from typing import cast | ||
|
|
||
| import pytest | ||
| from pydantic import ValidationError | ||
|
|
||
| from verifiers.v1.clients import ModelContext | ||
| from verifiers.v1.configs.client import EvalClientConfig | ||
| from verifiers.v1.harnesses.cline import ClineHarness, ClineHarnessConfig | ||
| from verifiers.v1.runtimes import ProgramResult, Runtime | ||
| from verifiers.v1.task import TaskData | ||
| from verifiers.v1.trace import Trace | ||
| from verifiers.v1.types import Sampling | ||
|
|
||
|
|
||
| class RecordingRuntime: | ||
| def __init__(self) -> None: | ||
| self.commands: list[tuple[list[str], dict[str, str]]] = [] | ||
| self.programs: list[tuple[list[str], dict[str, str]]] = [] | ||
| self.files: dict[str, bytes] = {} | ||
|
|
||
| async def write(self, path: str, data: bytes) -> None: | ||
| self.files[path] = data | ||
|
|
||
| async def run(self, argv: list[str], env: dict[str, str]) -> ProgramResult: | ||
| self.commands.append((argv, env)) | ||
| return ProgramResult(exit_code=0, stdout="", stderr="") | ||
|
|
||
| async def run_program(self, argv: list[str], env: dict[str, str]) -> ProgramResult: | ||
| self.programs.append((argv, env)) | ||
| return ProgramResult(exit_code=0, stdout="", stderr="") | ||
|
|
||
|
|
||
| def test_cline_config_validates_pinned_version_and_retries() -> None: | ||
| config = ClineHarnessConfig(id="cline") | ||
| assert config.version == "3.0.57" | ||
| assert config.compaction == "basic" | ||
| assert config.max_retries == 6 | ||
|
|
||
| with pytest.raises(ValidationError): | ||
| ClineHarnessConfig(id="cline", version="3.0.57; unsafe") | ||
| with pytest.raises(ValidationError): | ||
| ClineHarnessConfig(id="cline", max_retries=0) | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_cline_launch_wires_interception_and_restricts_tools() -> None: | ||
| runtime = RecordingRuntime() | ||
| harness = ClineHarness( | ||
| ClineHarnessConfig(id="cline", disabled_tools=["custom_external_tool"]) | ||
| ) | ||
| ctx = ModelContext( | ||
| model="openai/example", | ||
| client=EvalClientConfig(), | ||
| sampling=Sampling(reasoning_effort="medium"), | ||
| ) | ||
| trace = Trace.model_construct(id="trace-id") | ||
|
|
||
| result = await harness.launch( | ||
| ctx, | ||
| trace, | ||
| cast(Runtime, runtime), | ||
| "http://interception.test/v1", | ||
| "ephemeral-secret", | ||
| {}, | ||
| TaskData(prompt="solve the task", system_prompt="follow the rules"), | ||
| ) | ||
|
|
||
| assert result.exit_code == 0 | ||
| settings = runtime.files[ | ||
| "/tmp/vf-cline/trace-id/settings/global-settings.json" | ||
| ].decode() | ||
| assert '"fetch_web_content"' in settings | ||
| assert '"spawn_agent"' in settings | ||
| assert '"custom_external_tool"' in settings | ||
| assert ( | ||
| runtime.files["/tmp/vf-cline/trace-id/settings/cline_mcp_settings.json"] | ||
| == b'{"mcpServers":{}}' | ||
| ) | ||
|
|
||
| auth, _ = runtime.commands[-1] | ||
| assert auth[auth.index("--baseurl") + 1] == "http://interception.test/v1" | ||
| assert auth[auth.index("--modelid") + 1] == "openai/example" | ||
|
|
||
| argv, env = runtime.programs[-1] | ||
| assert argv[argv.index("--key") + 1] == "ephemeral-secret" | ||
| assert argv[argv.index("--model") + 1] == "openai/example" | ||
| assert argv[argv.index("--thinking") + 1] == "medium" | ||
| assert argv[-1] == "follow the rules\n\nsolve the task" | ||
| assert env["CLINE_TELEMETRY_DISABLED"] == "1" | ||
| assert env["CLINE_MCP_SETTINGS_PATH"].endswith("cline_mcp_settings.json") | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_cline_rejects_mcp_before_starting_program() -> None: | ||
| runtime = RecordingRuntime() | ||
| harness = ClineHarness(ClineHarnessConfig(id="cline")) | ||
| ctx = ModelContext(model="openai/example", client=EvalClientConfig()) | ||
|
|
||
| with pytest.raises(ValueError, match="does not support MCP"): | ||
| await harness.launch( | ||
| ctx, | ||
| Trace.model_construct(id="trace-id"), | ||
| cast(Runtime, runtime), | ||
| "http://interception.test/v1", | ||
| "ephemeral-secret", | ||
| {"server": "http://mcp.test"}, | ||
| TaskData(prompt="solve the task"), | ||
| ) | ||
|
|
||
| assert runtime.programs == [] | ||
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # Cline harness | ||
|
|
||
| The `cline` harness runs the public Cline CLI inside the selected verifiers | ||
| runtime and routes every model call through the rollout's interception server. | ||
| Cline's native OpenAI-compatible tool calls, results, usage, and final response | ||
| therefore become ordinary verifiers trace nodes and model-call records. | ||
|
|
||
| ## Quickstart | ||
|
|
||
| Choose an isolated container or VM runtime. The harness installs its pinned Cline | ||
| release during trusted setup. | ||
|
|
||
| ```bash | ||
| export OPENAI_API_KEY=... | ||
| uv run eval MY_TASKSET \ | ||
| --env.agent.harness.id cline \ | ||
| --env.agent.runtime.type docker \ | ||
| --model openai/gpt-4.1-mini \ | ||
| --client.base-url https://openrouter.ai/api/v1 \ | ||
| --client.api-key-var OPENAI_API_KEY \ | ||
| --no-push | ||
| ``` | ||
|
|
||
| `--no-push` keeps the run local. Inspect the resolved config and traces under | ||
| `outputs/<run>/configs/eval.json` and `outputs/<run>/traces.jsonl`. | ||
|
|
||
| ## Harness configuration | ||
|
|
||
| ```toml | ||
| [env.agent.harness] | ||
| id = "cline" | ||
| version = "3.0.57" | ||
| compaction = "basic" # basic | agentic | off | ||
| max_retries = 6 | ||
| disabled_tools = [] # additional native Cline tool names | ||
| ``` | ||
|
|
||
| Use the agent and sampling blocks for settings owned by verifiers: | ||
|
|
||
| ```toml | ||
| [env.agent] | ||
| max_turns = 20 | ||
| max_output_tokens = 32768 | ||
|
|
||
| [env.agent.timeout] | ||
| setup = 600 | ||
| rollout = 1800 | ||
|
|
||
| [sampling] | ||
| temperature = 0.2 | ||
| ``` | ||
|
|
||
| The interception server applies the configured model and sampling values to | ||
| Cline's requests before forwarding them upstream. | ||
|
|
||
| ## Security posture and limitations | ||
|
|
||
| - Each rollout gets an isolated Cline data directory, deleted after scoring. | ||
| - Cline telemetry and update checks are disabled. | ||
| - Cline MCP configuration starts empty. The harness does not advertise MCP or | ||
| resume support yet. | ||
| - Network/human/nested-agent tools are disabled by default. The retained tools | ||
| read, search, edit, and run commands in the task runtime. | ||
| - The adapter uses Cline's one-shot headless mode. Multi-turn tool use inside one | ||
| task is captured, but env-driven user simulation requires future ACP support. | ||
| - Cline `3.0.57` has no max-step flag. Use verifiers' `max_turns`, token caps, and | ||
| rollout timeout; their stop conditions and errors are recorded on the trace. | ||
| - Eval-client traces preserve message/tool structure and provider-reported usage, | ||
| but exact token IDs and masks require a compatible training client/renderer. | ||
| - Compaction was not forced in the smoke validation. If Cline rewrites history, | ||
| inspect the resulting branches before using those samples for training. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from verifiers.v1.harnesses.cline.harness import ClineHarness, ClineHarnessConfig | ||
|
|
||
| __all__ = ["ClineHarness", "ClineHarnessConfig"] |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.