Skip to content

run_repo

View module diagram

Repo-level operations for the run lifecycle layer.

This module defines the RunRepoManager protocol and its GitRunRepoManager implementation for repo-level operations that the run composition layer needs — capturing the current HEAD and cleaning up worktree branches during resume.

Protocols

RunRepoManager: Repo operations for run lifecycle management.

Classes:

Name Description
GitRunRepoManager

Git-based implementation.

RunRepoManager

Bases: Protocol

Repo operations needed by the run lifecycle layer.

Abstracts the git operations that run_graph uses so the composition layer does not depend on ops.git directly.

Methods:

Name Description
current_head

Return the current HEAD SHA.

reset_stale_worktree_branches

Clean up worktrees for resume.

Source code in src/agentrelay/run_repo.py
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
@runtime_checkable
class RunRepoManager(Protocol):
    """Repo operations needed by the run lifecycle layer.

    Abstracts the git operations that ``run_graph`` uses so the
    composition layer does not depend on ``ops.git`` directly.

    Methods:
        current_head: Return the current HEAD SHA.
        reset_stale_worktree_branches: Clean up worktrees for resume.
    """

    def current_head(self) -> str:
        """Return the current HEAD SHA of the repository.

        Returns:
            The full commit SHA string.
        """
        ...

    def reset_stale_worktree_branches(
        self,
        graph: TaskGraph,
        frozen_task_ids: set[str],
    ) -> None:
        """Switch worktrees off non-frozen task branches for clean resume.

        When a worktree is checked out on a non-frozen task's branch,
        the task preparer would treat it as a retry (preserving old WIP
        commits).  Switching to the integration branch forces the
        preparer to force-create a clean task branch.

        Args:
            graph: Current graph definition.
            frozen_task_ids: Task IDs that are frozen (should not be
                touched).
        """
        ...

current_head()

Return the current HEAD SHA of the repository.

Returns:

Type Description
str

The full commit SHA string.

Source code in src/agentrelay/run_repo.py
36
37
38
39
40
41
42
def current_head(self) -> str:
    """Return the current HEAD SHA of the repository.

    Returns:
        The full commit SHA string.
    """
    ...

reset_stale_worktree_branches(graph, frozen_task_ids)

Switch worktrees off non-frozen task branches for clean resume.

When a worktree is checked out on a non-frozen task's branch, the task preparer would treat it as a retry (preserving old WIP commits). Switching to the integration branch forces the preparer to force-create a clean task branch.

Parameters:

Name Type Description Default
graph TaskGraph

Current graph definition.

required
frozen_task_ids set[str]

Task IDs that are frozen (should not be touched).

required
Source code in src/agentrelay/run_repo.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def reset_stale_worktree_branches(
    self,
    graph: TaskGraph,
    frozen_task_ids: set[str],
) -> None:
    """Switch worktrees off non-frozen task branches for clean resume.

    When a worktree is checked out on a non-frozen task's branch,
    the task preparer would treat it as a retry (preserving old WIP
    commits).  Switching to the integration branch forces the
    preparer to force-create a clean task branch.

    Args:
        graph: Current graph definition.
        frozen_task_ids: Task IDs that are frozen (should not be
            touched).
    """
    ...

GitRunRepoManager

Git-based implementation of run lifecycle repo operations.

Attributes:

Name Type Description
repo_path

Path to the repository root.

graph_name

Name of the graph (used for branch naming conventions and worktree paths).

Source code in src/agentrelay/run_repo.py
 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
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class GitRunRepoManager:
    """Git-based implementation of run lifecycle repo operations.

    Attributes:
        repo_path: Path to the repository root.
        graph_name: Name of the graph (used for branch naming
            conventions and worktree paths).
    """

    def __init__(self, repo_path: Path, graph_name: str) -> None:
        self.repo_path = repo_path
        self.graph_name = graph_name

    def current_head(self) -> str:
        """Return the current HEAD SHA via ``git rev-parse HEAD``.

        Returns:
            The full commit SHA string.
        """
        return git.rev_parse_head(self.repo_path)

    def reset_stale_worktree_branches(
        self,
        graph: TaskGraph,
        frozen_task_ids: set[str],
    ) -> None:
        """Switch worktrees off stale task branches before dispatch.

        Iterates all workstreams in the graph.  For each worktree that
        is checked out on a non-frozen task branch, switches to the
        integration branch and removes untracked files.  This ensures
        new agents get a coherent fresh start rather than seeing partial
        artifacts from an interrupted prior run.

        Args:
            graph: Current graph definition.
            frozen_task_ids: Task IDs that are frozen (should not be
                touched).
        """
        task_branch_prefix = f"agentrelay/{self.graph_name}/"
        for ws_id in graph.workstream_ids():
            worktree_path = self.repo_path / ".worktrees" / self.graph_name / ws_id
            if not worktree_path.is_dir():
                continue

            try:
                current = git.current_branch(worktree_path)
            except Exception:
                continue

            if current is None:
                continue

            # Task branches: agentrelay/<graph>/<task_id> (no further slashes).
            # Integration branches have an extra /integration suffix.
            if not current.startswith(task_branch_prefix):
                continue
            suffix = current[len(task_branch_prefix) :]
            if "/" in suffix:
                continue
            task_id = suffix
            if task_id in frozen_task_ids:
                continue

            # Switch to integration branch and clean untracked files.
            integration_branch = f"agentrelay/{self.graph_name}/{ws_id}/integration"
            git.checkout(worktree_path, integration_branch)
            git.clean(worktree_path)

current_head()

Return the current HEAD SHA via git rev-parse HEAD.

Returns:

Type Description
str

The full commit SHA string.

Source code in src/agentrelay/run_repo.py
77
78
79
80
81
82
83
def current_head(self) -> str:
    """Return the current HEAD SHA via ``git rev-parse HEAD``.

    Returns:
        The full commit SHA string.
    """
    return git.rev_parse_head(self.repo_path)

reset_stale_worktree_branches(graph, frozen_task_ids)

Switch worktrees off stale task branches before dispatch.

Iterates all workstreams in the graph. For each worktree that is checked out on a non-frozen task branch, switches to the integration branch and removes untracked files. This ensures new agents get a coherent fresh start rather than seeing partial artifacts from an interrupted prior run.

Parameters:

Name Type Description Default
graph TaskGraph

Current graph definition.

required
frozen_task_ids set[str]

Task IDs that are frozen (should not be touched).

required
Source code in src/agentrelay/run_repo.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def reset_stale_worktree_branches(
    self,
    graph: TaskGraph,
    frozen_task_ids: set[str],
) -> None:
    """Switch worktrees off stale task branches before dispatch.

    Iterates all workstreams in the graph.  For each worktree that
    is checked out on a non-frozen task branch, switches to the
    integration branch and removes untracked files.  This ensures
    new agents get a coherent fresh start rather than seeing partial
    artifacts from an interrupted prior run.

    Args:
        graph: Current graph definition.
        frozen_task_ids: Task IDs that are frozen (should not be
            touched).
    """
    task_branch_prefix = f"agentrelay/{self.graph_name}/"
    for ws_id in graph.workstream_ids():
        worktree_path = self.repo_path / ".worktrees" / self.graph_name / ws_id
        if not worktree_path.is_dir():
            continue

        try:
            current = git.current_branch(worktree_path)
        except Exception:
            continue

        if current is None:
            continue

        # Task branches: agentrelay/<graph>/<task_id> (no further slashes).
        # Integration branches have an extra /integration suffix.
        if not current.startswith(task_branch_prefix):
            continue
        suffix = current[len(task_branch_prefix) :]
        if "/" in suffix:
            continue
        task_id = suffix
        if task_id in frozen_task_ids:
            continue

        # Switch to integration branch and clean untracked files.
        integration_branch = f"agentrelay/{self.graph_name}/{ws_id}/integration"
        git.checkout(worktree_path, integration_branch)
        git.clean(worktree_path)