Skip to content
Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ Skills: `coral-quickstart` (install → setup → `.coral_workspace/`), `setting
|-------|------------------|
| [Claude Code](https://github.com/anthropics/claude-code) — default | `claude_code` |
| [Codex](https://github.com/openai/codex) | `codex` |
| [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | `dsh` |
| [Cursor Agent](https://cursor.com/docs/cli/overview) | `cursor` |
| [Kiro](https://kiro.dev) | `kiro` |
| [OpenCode](https://github.com/opencode-ai/opencode) | `opencode` |
| [Pi](https://pi.dev) | `pi` |

Each agent must be installed and authenticated separately. Per-runtime config — including the [LiteLLM gateway](https://coral.compounding-intelligence.ai/docs/guides/gateway) for custom models — is documented at [Agent Runtimes](https://coral.compounding-intelligence.ai/docs/guides/agent-runtimes).

Expand Down
15 changes: 15 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ codex plugin add coral@coral-marketplace
|-------|------------------|
| [Claude Code](https://github.com/anthropics/claude-code) —— 默认 | `claude_code` |
| [Codex](https://github.com/openai/codex) | `codex` |
| [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | `dsh` |
| [Cursor Agent](https://cursor.com/docs/cli/overview) | `cursor` |
| [Kiro](https://kiro.dev) | `kiro` |
| [OpenCode](https://github.com/opencode-ai/opencode) | `opencode` |
| [Pi](https://pi.dev) | `pi` |

每个 Agent 需自行安装并完成认证。各运行时的详细配置(含[ LiteLLM Gateway](https://coral.compounding-intelligence.ai/docs/guides/gateway) 自定义模型代理)见 [Agent 运行时文档](https://coral.compounding-intelligence.ai/docs/guides/agent-runtimes)。

Expand Down Expand Up @@ -136,6 +138,9 @@ uv run ruff check .
uv run ruff format .
```

> [!IMPORTANT]
> **Docker 要求:**部分内置 grader(例如 SWE-bench、terminal-bench)使用 [Harbor](https://github.com/corca-ai/harbor) 在 Docker 容器中执行评估。此时 CORAL 本身**不能**运行在 Docker 中,因为不支持 Docker-in-Docker(DinD)。请直接在宿主机上运行 CORAL。

### 参与贡献

欢迎社区贡献 —— bug 报告、`examples/` 下的新任务、新的 agent runtime、文档改进,都很欢迎。先看这里:
Expand All @@ -160,6 +165,16 @@ uv run ruff format .
}
```

## Star History

<a href="https://www.star-history.com/?repos=Human-Agent-Society%2FCORAL&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Human-Agent-Society/CORAL&type=date&theme=dark&legend=top-left&sealed_token=_Mr1XWVeoHdhN5RB0i4Fz-C0qC_ci1us7BViejAd73QFLQB7w_FW2o-3uWeM_nqAmLucjkLC8pNTEHRR1MVZ7LKGHYmhes0XBWUMTHuAWyJUJeYjB5XCNw" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Human-Agent-Society/CORAL&type=date&legend=top-left&sealed_token=_Mr1XWVeoHdhN5RB0i4Fz-C0qC_ci1us7BViejAd73QFLQB7w_FW2o-3uWeM_nqAmLucjkLC8pNTEHRR1MVZ7LKGHYmhes0XBWUMTHuAWyJUJeYjB5XCNw" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Human-Agent-Society/CORAL&type=date&legend=top-left&sealed_token=_Mr1XWVeoHdhN5RB0i4Fz-C0qC_ci1us7BViejAd73QFLQB7w_FW2o-3uWeM_nqAmLucjkLC8pNTEHRR1MVZ7LKGHYmhes0XBWUMTHuAWyJUJeYjB5XCNw" />
</picture>
</a>

### 致谢

我们感谢 [TNT Accelerator](https://www.tnt.so/) 提供的慷慨支持,包括在开发过程中给予帮助的各种 API 积分。也要感谢许多如 [OpenEvolve](https://github.com/algorithmicsuperintelligence/openevolve)、[autoresearch](https://github.com/karpathy/autoresearch)、[TTT Discover](https://arxiv.org/abs/2601.16175) 等的十分有启发性的工作,这些工作为 Coral 的诞生奠定了基础。
226 changes: 226 additions & 0 deletions coral/agent/builtin/deepseek_harness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
"""DeepSeek Harness (``dsh``) CLI subprocess lifecycle."""

from __future__ import annotations

import json
import logging
import subprocess
import sys
import threading
from pathlib import Path
from typing import Any

from coral.agent.exit_classifier import classify_by_uptime
from coral.agent.process import open_agent_stderr_for_log_dir
from coral.agent.runtime import (
AgentHandle,
apply_run_as_user,
apply_sandbox,
apply_sandbox_env,
write_coral_log_entry,
)
from coral.sandbox.protocol import AgentSandboxSpec
from coral.workspace.repo import _clean_env

logger = logging.getLogger(__name__)

_DEEPSEEK_HARNESS_RUNTIME_OPTION_KEYS = {
"command",
"profile",
"patch",
"provider",
"permission_mode",
"tools_mode",
}


class DeepSeekHarnessRuntime:
"""Spawn and manage the official DeepSeek Harness headless CLI."""

@property
def instruction_filename(self) -> str:
return "AGENTS.md"

@property
def shared_dir_name(self) -> str:
return ".dsh"

def extract_session_id(self, log_path: Path) -> str | None:
# The shipped headless profile creates a fresh persisted Agent, but its
# stdout contract exposes only the final assistant text (not a session
# id). Keep this explicit rather than guessing from user-facing output.
return None

def classify_exit(
self,
log_path: Path,
exit_code: int | None,
uptime_seconds: float | None,
min_clean_runtime_seconds: int = 60,
) -> str:
return classify_by_uptime(exit_code, uptime_seconds, min_clean_runtime_seconds)

def start(
self,
worktree_path: Path,
coral_md_path: Path,
model: str = "deepseek-v4-flash",
runtime_options: dict[str, Any] | None = None,
max_turns: int = 0,
log_dir: Path | None = None,
verbose: bool = False,
resume_session_id: str | None = None,
prompt: str | None = None,
prompt_source: str | None = None,
task_name: str | None = None,
task_description: str | None = None,
gateway_url: str | None = None,
gateway_api_key: str | None = None,
run_as_user: dict[str, Any] | None = None,
sandbox: AgentSandboxSpec | None = None,
) -> AgentHandle:
"""Start ``dsh --profile headless`` in the given worktree.

DeepSeek Harness currently exposes no session-resume flag from its
headless profile. A CORAL restart therefore starts a new dsh session
and supplies the normal continuation prompt.
"""
agent_id_file = worktree_path / ".coral_agent_id"
agent_id = agent_id_file.read_text().strip() if agent_id_file.exists() else "unknown"

if log_dir is None:
log_dir = worktree_path / ".dsh" / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
log_idx = len(list(log_dir.glob(f"{agent_id}*.log")))
log_path = log_dir / f"{agent_id}.{log_idx}.log"

if prompt is None:
if resume_session_id:
prompt = "Session restarted. Inspect the workspace and continue the task where it left off."
else:
prompt = "Begin working on your task and iterating on the seed solution."

opts = runtime_options or {}
for key in opts:
if key not in _DEEPSEEK_HARNESS_RUNTIME_OPTION_KEYS:
logger.warning(f"Ignoring unsupported deepseek_harness runtime option: {key}")

command = str(opts.get("command") or "dsh")
profile = str(opts.get("profile") or "headless")
cmd = [command, "--profile", profile]
patches = opts.get("patch")
if isinstance(patches, (str, Path)):
patches = [patches]
if isinstance(patches, list):
for patch in patches:
cmd.extend(["--patch", str(patch)])

# The headless CLI has no --model flag. A final Cordis overlay keeps
# CORAL's agents.model contract authoritative over profile defaults and
# any user-supplied patches.
dsh_home = worktree_path / ".dsh"
dsh_home.mkdir(parents=True, exist_ok=True)
model_patch_path = dsh_home / "coral-model.patch.yml"
provider = str(opts.get("provider") or "deepseek-official")
model_patch_path.write_text(
"- id: agent-default-model\n"
" config:\n"
f" provider: {json.dumps(provider)}\n"
f" model: {json.dumps(model)}\n"
)
cmd.extend(["--patch", str(model_patch_path)])
cmd.append(prompt)
cmd = apply_sandbox(cmd, sandbox)

logger.info(f"Starting DeepSeek Harness agent {agent_id} in {worktree_path}")
logger.info(f"Command: {' '.join(cmd)}")

agent_env = _clean_env()
worktree_venv = str(worktree_path / ".venv")
agent_env["UV_PROJECT_ENVIRONMENT"] = worktree_venv
agent_env["VIRTUAL_ENV"] = worktree_venv
agent_env["PATH"] = str(worktree_path / ".venv" / "bin") + ":" + agent_env.get("PATH", "")
agent_env["DSH_HOME"] = str(dsh_home)

permission_mode = opts.get("permission_mode")
if permission_mode:
agent_env["DSH_PERMISSION_MODE"] = str(permission_mode)
tools_mode = opts.get("tools_mode")
if tools_mode:
agent_env["DSH_TOOLS_MODE"] = str(tools_mode)
if gateway_url:
agent_env["DEEPSEEK_BASE_URL"] = gateway_url
if gateway_api_key:
agent_env["DEEPSEEK_API_KEY"] = gateway_api_key

apply_sandbox_env(agent_env, sandbox)
user_kwargs = apply_run_as_user(agent_env, run_as_user)

log_file = open(log_path, "w", buffering=1)
err_path: Path | None = None
err_file: Any = None
stderr_target: Any = subprocess.STDOUT
opened = open_agent_stderr_for_log_dir(log_dir, agent_id)
if opened is not None:
err_path, err_file = opened
stderr_target = err_file

write_coral_log_entry(
log_file,
prompt=prompt,
source=prompt_source or ("restart" if resume_session_id else "start"),
agent_id=agent_id,
session_id=None,
task_name=task_name,
task_description=task_description,
)

if verbose:
process = subprocess.Popen(
cmd,
cwd=str(worktree_path),
stdout=subprocess.PIPE,
stderr=stderr_target,
start_new_session=True,
env=agent_env,
**user_kwargs,
)

def _tee_output(proc: subprocess.Popen, log_f: Any, agent: str) -> None:
try:
if proc.stdout is None:
return
for line in iter(proc.stdout.readline, b""):
decoded = line.decode("utf-8", errors="replace")
sys.stdout.write(f"[{agent}] {decoded}")
sys.stdout.flush()
log_f.write(decoded)
log_f.flush()
finally:
log_f.close()

threading.Thread(
target=_tee_output, args=(process, log_file, agent_id), daemon=True
).start()
log_file_ref = None
else:
process = subprocess.Popen(
cmd,
cwd=str(worktree_path),
stdout=log_file,
stderr=stderr_target,
start_new_session=True,
env=agent_env,
**user_kwargs,
)
log_file_ref = log_file

return AgentHandle(
agent_id=agent_id,
process=process,
worktree_path=worktree_path,
log_path=log_path,
_log_file=log_file_ref,
err_file=err_file,
err_path=err_path,
)
7 changes: 7 additions & 0 deletions coral/agent/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from coral.agent.builtin.claude_code import ClaudeCodeRuntime
from coral.agent.builtin.codex import CodexRuntime
from coral.agent.builtin.cursor_agent import CursorAgentRuntime
from coral.agent.builtin.deepseek_harness import DeepSeekHarnessRuntime
from coral.agent.builtin.kiro import KiroRuntime
from coral.agent.builtin.opencode import OpenCodeRuntime
from coral.agent.builtin.pi_agent import PiAgentRuntime
Expand All @@ -17,6 +18,7 @@
"claude_code": ClaudeCodeRuntime,
"codex": CodexRuntime,
"cursor_agent": CursorAgentRuntime,
"dsh": DeepSeekHarnessRuntime,
"kiro": KiroRuntime,
"opencode": OpenCodeRuntime,
"pi": PiAgentRuntime,
Expand All @@ -32,6 +34,9 @@
"kiro-cli": "kiro",
"cursor": "cursor_agent",
"cursor-agent": "cursor_agent",
"deepseek": "dsh",
"deepseek-harness": "dsh",
"deepseek_harness": "dsh",
"pi-agent": "pi",
}

Expand All @@ -40,6 +45,7 @@
"claude_code": "sonnet",
"codex": "gpt-5.4",
"cursor_agent": "auto",
"dsh": "deepseek-v4-flash",
"kiro": "auto",
"opencode": "openai/gpt-5",
"pi": "zai/glm-5.1",
Expand All @@ -52,6 +58,7 @@
"claude_code": "claude",
"codex": "codex",
"cursor_agent": "cursor-agent",
"dsh": "dsh",
"kiro": "kiro-cli",
"opencode": "opencode",
"pi": "pi",
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/api/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class GraderConfig:
@dataclass
class AgentConfig:
count: int # Number of agents (default: 1)
runtime: str # "claude_code", "codex", "opencode"
runtime: str # "claude_code", "codex", "dsh", "opencode", ...
model: str # Model name or ID (default: "sonnet")
max_turns: int # Max conversation turns (default: 200)
timeout: int # Session timeout in seconds (default: 3600)
Expand Down
5 changes: 3 additions & 2 deletions docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ model. No credentials are ever stored.

### `coral setup`

Scan `PATH` for installed agent runtime CLIs (`claude`, `codex`,
Scan `PATH` for installed agent runtime CLIs (`claude`, `codex`, `dsh`,
`cursor-agent`, `opencode`, `kiro-cli`, `pi`) and, in an interactive terminal,
launch a numbered-selection wizard to create bindings. Pick one or more
runtimes (`1`, `1,3`, `1-3`, or `all`) and the wizard prompts for binding
Expand Down Expand Up @@ -89,7 +89,7 @@ coral setup agent [--name NAME] [--runtime RUNTIME] [--command PATH]
| Flag | Description |
|------|-------------|
| `--name` | Binding name (e.g. `claude-opus`) |
| `--runtime` | Runtime: `claude_code`, `codex`, `opencode`, `cursor_agent`, `kiro`, `pi`, or a `module.path:ClassName` entrypoint |
| `--runtime` | Runtime: `claude_code`, `codex`, `dsh`, `opencode`, `cursor_agent`, `kiro`, `pi`, or a `module.path:ClassName` entrypoint |
| `--command` | CLI binary (defaults to the runtime's command) |
| `--model` | Default model for this binding |
| `--role-file` | Path to a role seed `.md` file |
Expand All @@ -100,6 +100,7 @@ coral setup agent [--name NAME] [--runtime RUNTIME] [--command PATH]
```bash
coral setup agent --name claude-opus --runtime claude_code --model opus
coral setup agent --name codex-high --runtime codex --option model_reasoning_effort=high
coral setup agent --name deepseek --runtime dsh --model deepseek-v4-flash
```

### `coral agents`
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/concepts/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Agents are the optimizers in CORAL. Each agent is an autonomous coding subproces
|---------|-------------|-------------|
| Claude Code | `claude_code` | Anthropic's CLI agent (default) |
| Codex | `codex` | OpenAI's coding agent |
| DeepSeek Harness | `dsh` | DeepSeek's official agent harness |
| OpenCode | `opencode` | Open-source alternative |

Set the runtime in your `task.yaml`:
Expand Down Expand Up @@ -93,4 +94,3 @@ Each agent gets:
- Access to all other agents' attempts (scores and diffs)
- Shared notes for communicating insights
- Shared skills for reusable tools

2 changes: 1 addition & 1 deletion docs/content/docs/getting-started/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ packaged-grader layout.
|-------|------|---------|-------------|
| `count` | int | `1` | Number of agents to spawn |
| `binding` | string | – | Name of a user-level [agent binding](/docs/guides/agent-bindings) to expand into `runtime`/`model`/`runtime_options`. Explicit fields here override the binding. |
| `runtime` | string | `"claude_code"` | Agent runtime: `claude_code`, `codex`, `opencode` |
| `runtime` | string | `"claude_code"` | Agent runtime: `claude_code`, `codex`, `dsh`, `opencode`, `cursor_agent`, `kiro`, or `pi` |
| `model` | string | `"sonnet"` | Model to use (e.g. `opus`, `sonnet`, `haiku`, or full model ID) |
| `max_turns` | int | `200` | Maximum conversation turns per agent |
| `timeout` | int | `3600` | Agent session timeout in seconds |
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/getting-started/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ At least one supported agent runtime:

- **Claude Code** (default) — requires an Anthropic API key
- **Codex** — OpenAI's coding agent
- **DeepSeek Harness** — the official `dsh` CLI; requires `DEEPSEEK_API_KEY`
- **OpenCode** — open-source alternative

For Harbor-based benchmarks (SWE-bench, terminal-bench):
Expand Down
4 changes: 2 additions & 2 deletions docs/content/docs/guides/agent-bindings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ agents:
## Creating a binding

The fastest path is `coral setup` with no subcommand — it scans `PATH` for
every supported runtime CLI (`claude`, `codex`, `cursor-agent`, `opencode`,
`kiro-cli`, `pi`) and offers an interactive numbered-selection wizard:
every supported runtime CLI (`claude`, `codex`, `dsh`, `cursor-agent`,
`opencode`, `kiro-cli`, `pi`) and offers an interactive numbered-selection wizard:

```bash
coral setup
Expand Down
Loading
Loading