Skip to content
Merged
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
5 changes: 0 additions & 5 deletions backend/druks/harnesses/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from .datastructures import (
AgentInvocation,
HarnessRunResult,
SandboxSettings,
)
from .providers import Provider, get_provider, is_registered

Expand Down Expand Up @@ -67,14 +66,10 @@ def __init__(
model: str | None,
fast_mode: bool,
effort: str | None,
sandbox: SandboxSettings | None = None,
) -> None:
self.model = model
self.fast_mode = fast_mode
self.effort = effort
# Optional only so argv-shape unit tests can build the harness without a
# sandbox-configured Settings; every real run needs it and raises when None.
self.sandbox = sandbox

@abstractmethod
async def build_invocation(self, session: AsyncSession, **kwargs: object) -> AgentInvocation:
Expand Down
16 changes: 4 additions & 12 deletions backend/druks/harnesses/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
from druks.sandbox.layout import get_runs_root
from druks.sandbox.models import SecretRef
from druks.secrets.models import VaultSecret
from druks.settings import load_settings
from druks.skills.models import Skill

from . import exceptions
from .artifacts import call_dir, write_cost
from .base import Harness
from .constants import CLAUDE_DISALLOWED_TOOLS
from .datastructures import SandboxSettings
from .providers import AnthropicProvider

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -113,12 +113,6 @@ async def build_invocation(
identity: dict | None = None,
timeout: int = Harness.default_timeout,
) -> AgentInvocation:
if not self.sandbox:
raise exceptions.HarnessError(
"claude harness requires sandbox settings — set sandbox.service_url and "
"related TOML settings.",
)

in_vm_run_dir = f"{get_runs_root(ssh_username)}/{run_id}"
in_vm_debug = f"{in_vm_run_dir}/debug.log"
in_vm_session = f"{in_vm_run_dir}/session.jsonl"
Expand Down Expand Up @@ -168,7 +162,6 @@ async def build_invocation(
stdin=prompt.encode("utf-8"),
credentials=await _get_credentials(
session,
self.sandbox,
include_plugins=include_plugins,
skills=skills,
),
Expand Down Expand Up @@ -241,7 +234,6 @@ def _command_args(self) -> tuple[str, ...]:

async def _get_credentials(
session: AsyncSession,
sandbox: SandboxSettings,
*,
include_plugins: bool = True,
skills: tuple[str, ...] = (),
Expand All @@ -251,7 +243,8 @@ async def _get_credentials(
placeholder for its token or key. ``include_plugins=False`` skips the
operator's plugin state, for prompts that use no MCP server and would
otherwise die on a misconfigured plugin."""
config_dir = sandbox.harness_config_root / ClaudeHarness.name
settings = load_settings()
config_dir = settings.harness_config_root / ClaudeHarness.name
home: list[HomeFile | HomeCopy] = []
claude_json = config_dir / ".claude.json"
if claude_json.is_file():
Expand All @@ -274,11 +267,10 @@ async def _get_credentials(
HomeCopy(".claude/plugins/marketplaces", plugins / "marketplaces"),
HomeCopy(".claude/plugins/cache", plugins / "cache"),
]
skills_dir = sandbox.skills_dir or config_dir / "skills"
home.append(
HomeCopy(
".claude/skills",
skills_dir,
settings.skills_dir,
excludes=await Skill.delivery_excludes(session, skills),
)
)
Expand Down
20 changes: 6 additions & 14 deletions backend/druks/harnesses/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@
from druks.sandbox.layout import get_runs_root, get_work_root
from druks.sandbox.models import SecretRef
from druks.secrets.models import VaultSecret
from druks.settings import load_settings
from druks.skills.models import Skill

from .artifacts import write_cost
from .base import Harness
from .datastructures import SandboxSettings
from .exceptions import (
HarnessAuthError,
HarnessError,
HarnessOverloadedError,
HarnessRateLimitError,
HarnessSpendLimitError,
Expand Down Expand Up @@ -466,13 +465,6 @@ async def build_invocation(
identity: dict | None = None,
timeout: int = Harness.default_timeout,
) -> AgentInvocation:
sandbox = self.sandbox
if not sandbox:
raise HarnessError(
f"{self.name} harness requires sandbox settings — set "
"sandbox.service_url and related TOML settings.",
)

cmd = self._build_codex_wrapper(
ssh_username=ssh_username,
schema=schema,
Expand All @@ -491,7 +483,7 @@ async def build_invocation(
name=self.name,
args=tuple(cmd),
stdin=_with_final_message_note(prompt).encode("utf-8"),
credentials=await self._get_credentials(session, sandbox, skills=skills),
credentials=await self._get_credentials(session, skills=skills),
env=extra_env,
extra_artifact_filenames=("output.json", "session.jsonl"),
)
Expand Down Expand Up @@ -566,17 +558,17 @@ def _prompt_flags(self) -> tuple[str, ...]:
return args

async def _get_credentials(
self, session: AsyncSession, sandbox: SandboxSettings, *, skills: tuple[str, ...] = ()
self, session: AsyncSession, *, skills: tuple[str, ...] = ()
) -> Credentials:
config_dir = sandbox.harness_config_root / self.name
skills_dir = sandbox.skills_dir or config_dir / "skills"
settings = load_settings()
config_dir = settings.harness_config_root / self.name
return Credentials(
home=(
HomeCopy(".codex/config.toml", config_dir / "config.toml"),
HomeCopy(".codex/AGENTS.md", config_dir / "AGENTS.md"),
HomeCopy(
".codex/skills",
skills_dir,
settings.skills_dir,
excludes=await Skill.delivery_excludes(session, skills),
),
)
Expand Down
35 changes: 0 additions & 35 deletions backend/druks/harnesses/datastructures.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
from collections.abc import Sequence
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Literal, Self

from pydantic import SecretStr

# Execution-side types live with the executor; re-exported here
# because the harness API speaks them.
from druks.sandbox.datastructures import ( # noqa: F401
AgentInvocation,
HarnessRunResult,
)
from druks.settings import Settings

Billing = Literal["subscription", "api_key"]

Expand Down Expand Up @@ -98,34 +94,3 @@ class ParsedUsage:
# should render "unmetered" rather than a quota that never moves.
unlimited: bool = False
raw: str = field(default="", repr=False)


@dataclass(frozen=True)
class SandboxSettings:
service_url: str
service_token: SecretStr
service_timeout: float
image: str
# Each harness owns one directory under this root. Missing files are not
# copied into the sandbox.
harness_config_root: Path
# Canonical shared-skills dir pushed into both ~/.claude/skills and
# ~/.codex/skills in the VM. ``None`` => per-CLI fallback (the skills
# subdir of each home).
skills_dir: Path | None = None

@classmethod
def from_settings(cls, settings: Settings) -> Self:
return cls(
service_url=settings.sandbox.service_url,
service_token=settings.sandbox.service_token,
service_timeout=settings.sandbox.timeout,
image=settings.sandbox.image,
harness_config_root=settings.harness_config_root,
skills_dir=settings.skills_dir,
)

@classmethod
def maybe_from_settings(cls, settings: Settings) -> Self | None:
if settings.sandbox.service_url:
return cls.from_settings(settings)
6 changes: 0 additions & 6 deletions backend/druks/harnesses/opencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,6 @@ async def build_invocation(
identity: dict | None = None,
timeout: int = Harness.default_timeout,
) -> AgentInvocation:
if not self.sandbox:
raise exceptions.HarnessError(
"opencode harness requires sandbox settings — set sandbox.service_url and "
"related TOML settings.",
)

mcp = {}
for server in mcp_servers:
headers = dict(server.headers)
Expand Down
6 changes: 0 additions & 6 deletions backend/druks/harnesses/pi.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ async def build_invocation(
identity: dict | None = None,
timeout: int = Harness.default_timeout,
) -> AgentInvocation:
if not self.sandbox:
raise exceptions.HarnessError(
f"{self.name} harness requires sandbox settings — set "
"sandbox.service_url and related TOML settings.",
)

model = self.model_id
provider = self.model.partition("/")[0]
in_vm_run_dir = f"{get_runs_root(ssh_username)}/{run_id}"
Expand Down
2 changes: 0 additions & 2 deletions backend/druks/sandbox/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from druks.core.utils.time import ensure_utc
from druks.durable.enums import AgentCallStatus
from druks.harnesses.artifacts import persist_manifest, persist_prompt, read_cost
from druks.harnesses.datastructures import SandboxSettings
from druks.harnesses.exceptions import (
HarnessError,
HarnessFirstByteTimeoutError,
Expand Down Expand Up @@ -231,7 +230,6 @@ async def run_agent(
model=model,
fast_mode=config.fast_mode,
effort=config.effort,
sandbox=SandboxSettings.maybe_from_settings(load_settings()),
)

# Names the artifact subdir and is the AgentCall.id — supplied by the
Expand Down
Loading
Loading