An Agentix agent integration is a Python package with a function that runs inside the sandbox. For CLI agents, that function usually starts a subprocess, waits for it to finish, and returns a typed result. There is no base class, registry, or framework-specific adapter layer. The integration is useful because callers can import it and write:
from agentix.agents.claude_code import run

result = await client.remote(run, instruction="fix the bug", workdir="/testbed")

Integration Contract

Keep the public surface narrow. A good first integration exposes one run function and one result type.
async def run(
    instruction: str,
    *,
    workdir: str = "/testbed",
    timeout: float = 600,
    env: dict[str, str] | None = None,
) -> RunResult:
    ...
The body runs in the sandbox, so it can call binaries, edit files, read the repository under /testbed, or invoke a local Python framework.

1. Package Layout

agentix-claude-code/
├── pyproject.toml
├── default.nix
└── src/
    └── __init__.py
Use default.nix when the agent needs a system binary that should be available on the worker PATH. Pure Python integrations can omit it.

2. Function Surface

src/__init__.py
from __future__ import annotations

import asyncio
import os
from dataclasses import dataclass


@dataclass
class RunResult:
    exit_code: int
    stdout: str
    stderr: str


async def run(
    instruction: str,
    *,
    workdir: str = "/testbed",
    timeout: float = 600,
    model: str | None = None,
    max_turns: int | None = None,
    env: dict[str, str] | None = None,
) -> RunResult:
    cmd = ["claude", "-p", instruction, "--print", "--permission-mode", "bypassPermissions"]
    if model:
        cmd += ["--model", model]
    if max_turns is not None:
        cmd += ["--max-turns", str(max_turns)]

    proc = await asyncio.create_subprocess_exec(
        *cmd,
        cwd=workdir,
        env={**os.environ, **(env or {})},
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )

    try:
        stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
    except TimeoutError:
        proc.kill()
        await proc.communicate()
        return RunResult(exit_code=-1, stdout="", stderr=f"timed out after {timeout}s")

    return RunResult(
        exit_code=proc.returncode or 0,
        stdout=stdout.decode(errors="replace"),
        stderr=stderr.decode(errors="replace"),
    )
The result type should describe the sandbox-side command, not the whole rollout. Patch extraction, scoring, and trace assembly usually belong in the caller that composes several remote calls.

3. Packaging

pyproject.toml
[project]
name = "agentix-agent-claude-code"
version = "0.1.0"
requires-python = ">=3.11"

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
package-dir = { "agentix.agents.claude_code" = "src" }
packages = ["agentix.agents.claude_code"]
After installation, callers can import the function normally:
from agentix.agents.claude_code import run
When a bundle includes this package, the sandbox worker can import the same module and execute run.

4. System Binary

Many agent CLIs are easiest to ship with Nix. The derivation should copy the executable into bin/. During agentix build, Agentix links those binaries under /nix/runtime/bin in the final image. See the plugins/agents/claude-code recipe for a complete working package.

5. Caller Composition

Keep generic rollout operations outside the agent wrapper. For example, extracting a patch is a shell operation that works for any agent that edited files:
from agentix.bash import run as bash_run

diff = await client.remote(
    bash_run,
    command="cd /testbed && git add -A && git diff --cached --no-color",
)
patch = diff.stdout
The caller decides how to compose repo setup, the agent run, patch extraction, scoring, and logging.

Side Channels

c.remote(...) is request/response: the host sends one call and gets one return value back. When the agent also needs to push progress, tokens, or LLM traffic to the host while work continues in the sandbox, use the generic agentix.sio namespace API alongside c.remote():
  • sandbox side: subclass agentix.Namespace, call await self.emit(...)
  • host side: subclass agentix.AsyncClientNamespace, register with client.register_namespace(...) before connecting
The abridge plugin follows this pattern: sandbox code forwards API requests over a plugin namespace; the host handler talks to the real upstream and replies on the same namespace. When side channels aren’t needed, keep a single run(...) that collects output internally and returns a RunResult when the CLI finishes — that is what agentix.agents.claude_code.run does today.

Boundaries

  • Pass secrets per call with env, for example env={"ANTHROPIC_API_KEY": "..."}.
  • Use a stable default workdir such as /testbed for benchmark repos.
  • Bound timeout aggressively. Agent CLIs can hang.
  • Keep scoring in a scorer module. Keep repo setup in a primitive such as agentix.bash.