Skip to content

runner

runner

High-level agent runner composing sandbox + agent config + container launch.

This is the core of terok-agent run — it builds the environment, prepares agent config, and launches a hardened Podman container with the requested AI agent. Three launch modes:

  • Headless: fire-and-forget with a prompt (run_headless)
  • Interactive: user logs in, agent is ready (run_interactive)
  • Web: toad served over HTTP (run_web)

All user config is runtime (env vars + volumes) — no L2 image build needed. Gate is on by default (safe-by-default egress control).

AgentRunner(*, sandbox=None, roster=None, base_image='ubuntu:24.04')

Composes sandbox + agent config into a single container launch.

All three run methods follow the same flow:

  1. Ensure L0+L1 images exist (build if missing)
  2. Prepare agent-config directory (wrapper, instructions, prompt)
  3. Assemble environment variables and volume mounts
  4. Optionally set up gate (mirror repo, create token)
  5. Launch container via podman
Source code in src/terok_agent/runner.py
def __init__(
    self,
    *,
    sandbox: Sandbox | None = None,
    roster: AgentRoster | None = None,
    base_image: str = "ubuntu:24.04",
) -> None:
    self._base_image = base_image
    self._sandbox: Sandbox | None = sandbox
    self._roster: AgentRoster | None = roster

sandbox property

Lazy-init sandbox facade.

roster property

Lazy-init agent roster.

run_headless(provider, repo, *, prompt, branch=None, model=None, max_turns=None, timeout=1800, gate=True, name=None, follow=False, unrestricted=True, gpu=False, hooks=None)

Launch a headless agent run. Returns container name.

The agent executes the prompt against repo (local path or git URL) and exits when done or when timeout is reached. Set follow=True to block until the agent finishes (the CLI does this by default).

Source code in src/terok_agent/runner.py
def run_headless(
    self,
    provider: str,
    repo: str,
    *,
    prompt: str,
    branch: str | None = None,
    model: str | None = None,
    max_turns: int | None = None,
    timeout: int = 1800,
    gate: bool = True,
    name: str | None = None,
    follow: bool = False,
    unrestricted: bool = True,
    gpu: bool = False,
    hooks: LifecycleHooks | None = None,
) -> str:
    """Launch a headless agent run. Returns container name.

    The agent executes the *prompt* against *repo* (local path or git URL)
    and exits when done or when *timeout* is reached.  Set *follow=True*
    to block until the agent finishes (the CLI does this by default).
    """
    return self._run(
        provider=provider,
        repo=repo,
        prompt=prompt,
        branch=branch,
        model=model,
        max_turns=max_turns,
        timeout=timeout,
        gate=gate,
        name=name,
        follow=follow,
        mode="headless",
        unrestricted=unrestricted,
        gpu=gpu,
        hooks=hooks,
    )

run_interactive(provider, repo, *, branch=None, gate=True, name=None, unrestricted=True, gpu=False, hooks=None)

Launch an interactive container. Returns container name.

The container stays up after init; user logs in via podman exec.

Source code in src/terok_agent/runner.py
def run_interactive(
    self,
    provider: str,
    repo: str,
    *,
    branch: str | None = None,
    gate: bool = True,
    name: str | None = None,
    unrestricted: bool = True,
    gpu: bool = False,
    hooks: LifecycleHooks | None = None,
) -> str:
    """Launch an interactive container. Returns container name.

    The container stays up after init; user logs in via ``podman exec``.
    """
    return self._run(
        provider=provider,
        repo=repo,
        branch=branch,
        gate=gate,
        name=name,
        mode="interactive",
        unrestricted=unrestricted,
        gpu=gpu,
        hooks=hooks,
    )

run_web(repo, *, port=None, branch=None, gate=True, name=None, public_url=None, unrestricted=True, gpu=False, hooks=None)

Launch a toad web container. Returns container name.

If port is None, an available port is auto-allocated.

Source code in src/terok_agent/runner.py
def run_web(
    self,
    repo: str,
    *,
    port: int | None = None,
    branch: str | None = None,
    gate: bool = True,
    name: str | None = None,
    public_url: str | None = None,
    unrestricted: bool = True,
    gpu: bool = False,
    hooks: LifecycleHooks | None = None,
) -> str:
    """Launch a toad web container. Returns container name.

    If *port* is None, an available port is auto-allocated.
    """
    if port is None:
        from terok_sandbox import find_free_port

        port = find_free_port()
    return self._run(
        provider="claude",  # toad uses claude as default
        repo=repo,
        branch=branch,
        gate=gate,
        name=name,
        mode="web",
        port=port,
        public_url=public_url,
        unrestricted=unrestricted,
        gpu=gpu,
        hooks=hooks,
    )

run_tool(tool, repo, *, tool_args=(), branch=None, gate=True, name=None, follow=True, timeout=600)

Launch a sidecar tool container. Returns container name.

Runs the named tool in a lightweight sidecar L1 image (no agent CLIs). The tool receives the real API key from the credential store — not a phantom token.

Source code in src/terok_agent/runner.py
def run_tool(
    self,
    tool: str,
    repo: str,
    *,
    tool_args: tuple[str, ...] = (),
    branch: str | None = None,
    gate: bool = True,
    name: str | None = None,
    follow: bool = True,
    timeout: int = 600,
) -> str:
    """Launch a sidecar tool container. Returns container name.

    Runs the named tool in a lightweight sidecar L1 image (no agent
    CLIs).  The tool receives the real API key from the credential
    store — not a phantom token.
    """
    return self._run(
        provider=tool,
        repo=repo,
        mode="tool",
        gate=gate,
        name=name,
        follow=follow,
        timeout=timeout,
        tool_args=tool_args,
        branch=branch,
    )