Skip to content

agent

Agent abstractions for live coding-agent execution.

This package defines how orchestrator/runtime code interacts with running agents: addressing types for locating an agent process and agent interfaces for sending kickoff instructions in a concrete execution environment.

Subpackages

core: Abstract interfaces (Agent ABC, AgentAddress ABC). implementations: Concrete implementations (TmuxAgent, TmuxAddress).

AgentAddress

Bases: ABC

Abstract base for addressing a running agent instance.

Defines the interface for locating a live agent regardless of execution environment. Concrete subclasses carry the environment-specific connection details needed to communicate with the agent (pane IDs, endpoints, etc.).

Attributes:

Name Type Description
label str

A human-readable string identifier for the agent's location.

Source code in src/agentrelay/agent/core/addressing.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class AgentAddress(ABC):
    """Abstract base for addressing a running agent instance.

    Defines the interface for locating a live agent regardless of execution
    environment. Concrete subclasses carry the environment-specific connection
    details needed to communicate with the agent (pane IDs, endpoints, etc.).

    Attributes:
        label: A human-readable string identifier for the agent's location.
    """

    @property
    @abstractmethod
    def label(self) -> str:
        """Return a human-readable identifier for this agent's location.

        Returns:
            String representation of the agent's address/location.
        """
        ...

    def teardown(
        self,
        signal_dir: Optional[Path] = None,
        keep_panes: bool = False,
    ) -> None:
        """Release environment-specific resources for this agent address.

        Called during task teardown to capture logs and clean up. The default
        implementation is a no-op; subclasses override for environment-specific
        cleanup (e.g. capturing tmux pane scrollback, killing windows).

        Args:
            signal_dir: Directory for writing teardown artifacts (e.g. agent logs).
                None if no signal directory is available.
            keep_panes: If True, preserve the agent's execution environment
                (e.g. tmux window) for debugging rather than destroying it.
        """

label abstractmethod property

Return a human-readable identifier for this agent's location.

Returns:

Type Description
str

String representation of the agent's address/location.

teardown(signal_dir=None, keep_panes=False)

Release environment-specific resources for this agent address.

Called during task teardown to capture logs and clean up. The default implementation is a no-op; subclasses override for environment-specific cleanup (e.g. capturing tmux pane scrollback, killing windows).

Parameters:

Name Type Description Default
signal_dir Optional[Path]

Directory for writing teardown artifacts (e.g. agent logs). None if no signal directory is available.

None
keep_panes bool

If True, preserve the agent's execution environment (e.g. tmux window) for debugging rather than destroying it.

False
Source code in src/agentrelay/agent/core/addressing.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def teardown(
    self,
    signal_dir: Optional[Path] = None,
    keep_panes: bool = False,
) -> None:
    """Release environment-specific resources for this agent address.

    Called during task teardown to capture logs and clean up. The default
    implementation is a no-op; subclasses override for environment-specific
    cleanup (e.g. capturing tmux pane scrollback, killing windows).

    Args:
        signal_dir: Directory for writing teardown artifacts (e.g. agent logs).
            None if no signal directory is available.
        keep_panes: If True, preserve the agent's execution environment
            (e.g. tmux window) for debugging rather than destroying it.
    """

Agent

Bases: ABC

Abstract base for a live running agent instance.

An Agent represents a coding assistant that has been spawned in a specific execution environment and can be instructed to begin work. Different execution environments (tmux, cloud, etc.) are represented by concrete subclasses.

The Agent does not store configuration or role information — these are accessible via runtime.task.primary_agent and runtime.task.role, ensuring a single source of truth.

Source code in src/agentrelay/agent/core/agent.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Agent(ABC):
    """Abstract base for a live running agent instance.

    An Agent represents a coding assistant that has been spawned in a specific
    execution environment and can be instructed to begin work. Different execution
    environments (tmux, cloud, etc.) are represented by concrete subclasses.

    The Agent does not store configuration or role information — these are
    accessible via runtime.task.primary_agent and runtime.task.role, ensuring
    a single source of truth.
    """

    @abstractmethod
    def send_kickoff(self, instructions_path: str) -> None:
        """Send kickoff instructions to the running agent.

        Activates the agent to begin autonomous work.

        Args:
            instructions_path: Path to the instructions file the agent should read.
                Typically the signal_dir/instructions.md file written by the orchestrator.
        """
        ...

    @property
    @abstractmethod
    def address(self) -> AgentAddress:
        """Return the address of this running agent.

        Returns:
            An AgentAddress indicating where the agent is running (type depends
            on the concrete Agent subclass).
        """
        ...

address abstractmethod property

Return the address of this running agent.

Returns:

Type Description
AgentAddress

An AgentAddress indicating where the agent is running (type depends

AgentAddress

on the concrete Agent subclass).

send_kickoff(instructions_path) abstractmethod

Send kickoff instructions to the running agent.

Activates the agent to begin autonomous work.

Parameters:

Name Type Description Default
instructions_path str

Path to the instructions file the agent should read. Typically the signal_dir/instructions.md file written by the orchestrator.

required
Source code in src/agentrelay/agent/core/agent.py
30
31
32
33
34
35
36
37
38
39
40
@abstractmethod
def send_kickoff(self, instructions_path: str) -> None:
    """Send kickoff instructions to the running agent.

    Activates the agent to begin autonomous work.

    Args:
        instructions_path: Path to the instructions file the agent should read.
            Typically the signal_dir/instructions.md file written by the orchestrator.
    """
    ...

TmuxAddress dataclass

Bases: AgentAddress

Address of an agent running in a tmux pane.

Attributes:

Name Type Description
session str

The name of the tmux session.

pane_id str

The identifier of the tmux pane (e.g., "%1", "%2").

Source code in src/agentrelay/agent/implementations/tmux_address.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@dataclass(frozen=True)
class TmuxAddress(AgentAddress):
    """Address of an agent running in a tmux pane.

    Attributes:
        session: The name of the tmux session.
        pane_id: The identifier of the tmux pane (e.g., "%1", "%2").
    """

    session: str
    pane_id: str

    @property
    def label(self) -> str:
        """Return a human-readable identifier combining session and pane.

        Returns:
            String in format "session:pane_id" (e.g., "agentrelay:%1").
        """
        return f"{self.session}:{self.pane_id}"

    def teardown(
        self,
        signal_dir: Optional[Path] = None,
        keep_panes: bool = False,
    ) -> None:
        """Capture pane scrollback and optionally kill the tmux window.

        Best-effort: errors are swallowed since the pane may already be gone.

        Args:
            signal_dir: If provided, pane scrollback is written as ``agent.log``.
            keep_panes: If True, keep the tmux window open for debugging.
        """
        try:
            log = tmux.capture_pane(self.pane_id, full_history=True)
            if signal_dir is not None:
                signals.write_text(signal_dir, "agent.log", log)
        except subprocess.CalledProcessError:
            pass  # Best-effort: pane may already be gone

        if not keep_panes:
            try:
                tmux.kill_window(self.pane_id)
            except subprocess.CalledProcessError:
                pass  # Best-effort

label property

Return a human-readable identifier combining session and pane.

Returns:

Type Description
str

String in format "session:pane_id" (e.g., "agentrelay:%1").

teardown(signal_dir=None, keep_panes=False)

Capture pane scrollback and optionally kill the tmux window.

Best-effort: errors are swallowed since the pane may already be gone.

Parameters:

Name Type Description Default
signal_dir Optional[Path]

If provided, pane scrollback is written as agent.log.

None
keep_panes bool

If True, keep the tmux window open for debugging.

False
Source code in src/agentrelay/agent/implementations/tmux_address.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def teardown(
    self,
    signal_dir: Optional[Path] = None,
    keep_panes: bool = False,
) -> None:
    """Capture pane scrollback and optionally kill the tmux window.

    Best-effort: errors are swallowed since the pane may already be gone.

    Args:
        signal_dir: If provided, pane scrollback is written as ``agent.log``.
        keep_panes: If True, keep the tmux window open for debugging.
    """
    try:
        log = tmux.capture_pane(self.pane_id, full_history=True)
        if signal_dir is not None:
            signals.write_text(signal_dir, "agent.log", log)
    except subprocess.CalledProcessError:
        pass  # Best-effort: pane may already be gone

    if not keep_panes:
        try:
            tmux.kill_window(self.pane_id)
        except subprocess.CalledProcessError:
            pass  # Best-effort

TmuxAgent dataclass

Bases: Agent

A live agent running in a tmux pane.

Represents a Claude Code instance launched in a tmux pane. The agent holds the TmuxAddress (session + pane_id) of its running process.

Attributes:

Name Type Description
_address TmuxAddress

The tmux address (session and pane_id) of the running agent.

Source code in src/agentrelay/agent/implementations/tmux_agent.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@dataclass
class TmuxAgent(Agent):
    """A live agent running in a tmux pane.

    Represents a Claude Code instance launched in a tmux pane. The agent holds
    the TmuxAddress (session + pane_id) of its running process.

    Attributes:
        _address: The tmux address (session and pane_id) of the running agent.
    """

    _address: TmuxAddress

    @property
    def address(self) -> TmuxAddress:
        """Return the tmux address of this agent."""
        return self._address

    @classmethod
    def from_config(
        cls,
        config: AgentConfig,
        task_id: str,
        worktree_path: Path,
        signal_dir: Path,
    ) -> TmuxAgent:
        """Create a tmux pane, launch Claude Code, and return the TmuxAgent handle.

        Creates a new tmux window in the session specified by config.environment,
        launches Claude Code with the specified model configuration, and returns
        a TmuxAgent object that can be used to communicate with the running process.

        Args:
            config: AgentConfig specifying framework, model, adr_verbosity, and
                environment. config.environment must be TmuxEnvironment.
                config.model is passed as --model flag (None = framework default).
            task_id: Identifier for this task (used as tmux window name).
            worktree_path: Path to the git worktree where the agent will work.
            signal_dir: Path to the signal directory where task files are written.

        Returns:
            A TmuxAgent instance with its address set to the created tmux pane.
        """
        session = config.environment.session
        pane_id = tmux.new_window(session, task_id, worktree_path)

        model_flag = f" --model {config.model}" if config.model else ""
        cmd = (
            f'AGENTRELAY_SIGNAL_DIR="{signal_dir}"'
            f" claude{model_flag}"
            f" --dangerously-skip-permissions"
        )
        tmux.send_keys(pane_id, cmd)

        return cls(_address=TmuxAddress(session=session, pane_id=pane_id))

    def send_kickoff(self, instructions_path: str) -> None:
        """Wait for the agent's TUI to be ready, then send the kickoff message.

        Sends an initial prompt to the agent pointing it to its task instructions.
        This activates the agent to begin autonomous work.

        The message sent is: "Read {instructions_path} and follow the steps exactly."

        Args:
            instructions_path: Path to the instructions file the agent should read.
                Typically the signal_dir/instructions.md file written by the orchestrator.
        """
        pane_id = self._address.pane_id
        tmux.wait_for_tui_ready(pane_id)
        tmux.send_keys(
            pane_id,
            f"Read {instructions_path} and follow the steps exactly.",
        )

address property

Return the tmux address of this agent.

from_config(config, task_id, worktree_path, signal_dir) classmethod

Create a tmux pane, launch Claude Code, and return the TmuxAgent handle.

Creates a new tmux window in the session specified by config.environment, launches Claude Code with the specified model configuration, and returns a TmuxAgent object that can be used to communicate with the running process.

Parameters:

Name Type Description Default
config AgentConfig

AgentConfig specifying framework, model, adr_verbosity, and environment. config.environment must be TmuxEnvironment. config.model is passed as --model flag (None = framework default).

required
task_id str

Identifier for this task (used as tmux window name).

required
worktree_path Path

Path to the git worktree where the agent will work.

required
signal_dir Path

Path to the signal directory where task files are written.

required

Returns:

Type Description
TmuxAgent

A TmuxAgent instance with its address set to the created tmux pane.

Source code in src/agentrelay/agent/implementations/tmux_agent.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@classmethod
def from_config(
    cls,
    config: AgentConfig,
    task_id: str,
    worktree_path: Path,
    signal_dir: Path,
) -> TmuxAgent:
    """Create a tmux pane, launch Claude Code, and return the TmuxAgent handle.

    Creates a new tmux window in the session specified by config.environment,
    launches Claude Code with the specified model configuration, and returns
    a TmuxAgent object that can be used to communicate with the running process.

    Args:
        config: AgentConfig specifying framework, model, adr_verbosity, and
            environment. config.environment must be TmuxEnvironment.
            config.model is passed as --model flag (None = framework default).
        task_id: Identifier for this task (used as tmux window name).
        worktree_path: Path to the git worktree where the agent will work.
        signal_dir: Path to the signal directory where task files are written.

    Returns:
        A TmuxAgent instance with its address set to the created tmux pane.
    """
    session = config.environment.session
    pane_id = tmux.new_window(session, task_id, worktree_path)

    model_flag = f" --model {config.model}" if config.model else ""
    cmd = (
        f'AGENTRELAY_SIGNAL_DIR="{signal_dir}"'
        f" claude{model_flag}"
        f" --dangerously-skip-permissions"
    )
    tmux.send_keys(pane_id, cmd)

    return cls(_address=TmuxAddress(session=session, pane_id=pane_id))

send_kickoff(instructions_path)

Wait for the agent's TUI to be ready, then send the kickoff message.

Sends an initial prompt to the agent pointing it to its task instructions. This activates the agent to begin autonomous work.

The message sent is: "Read {instructions_path} and follow the steps exactly."

Parameters:

Name Type Description Default
instructions_path str

Path to the instructions file the agent should read. Typically the signal_dir/instructions.md file written by the orchestrator.

required
Source code in src/agentrelay/agent/implementations/tmux_agent.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def send_kickoff(self, instructions_path: str) -> None:
    """Wait for the agent's TUI to be ready, then send the kickoff message.

    Sends an initial prompt to the agent pointing it to its task instructions.
    This activates the agent to begin autonomous work.

    The message sent is: "Read {instructions_path} and follow the steps exactly."

    Args:
        instructions_path: Path to the instructions file the agent should read.
            Typically the signal_dir/instructions.md file written by the orchestrator.
    """
    pane_id = self._address.pane_id
    tmux.wait_for_tui_ready(pane_id)
    tmux.send_keys(
        pane_id,
        f"Read {instructions_path} and follow the steps exactly.",
    )