# How Git Branch Attribution Works Per Prompt in Background Agents

> Understand how background agents manage Git branch attribution using session-level WebSocket messages, with prompts automatically inheriting the branch state.

- Repository: [Cole Murray/background-agents](https://github.com/ColeMurray/background-agents)
- Tags: internals
- Published: 2026-07-13

---

**Background Agents manages Git branch attribution at the session level, where a `session_branch` WebSocket message updates the branch state for the entire session, and each prompt automatically inherits this branch rather than carrying explicit branch metadata in its payload.**

The ColeMurray/background-agents repository implements a session-centric architecture for Git branch management that ensures consistency across multiple prompts. Unlike systems that embed branch information in each prompt request, this approach centralizes branch attribution in the session state, allowing agents to execute prompts against the currently selected branch without per-prompt configuration.

## Session-Level Branch State Management

In the Background Agents system, branch selection occurs during session initialization and persists across all subsequent prompts. When a user selects a branch in the web interface, the system dispatches a `session_branch` message to the control-plane WebSocket rather than attaching branch data to individual prompt requests.

### Dispatching Branch Updates via WebSocket

The branch selection UI utilizes the `useBranches` hook to fetch available branches and sends updates through a dedicated API endpoint. In [`packages/web/src/hooks/use-session-target-picker.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/hooks/use-session-target-picker.ts), the selection handler posts the branch change to the session:

```tsx
// packages/web/src/hooks/use-session-target-picker.ts
const { branches, loading: loadingBranches } = useBranches(repoOwner, repoName);
// ...
setSelectedBranch = (branch: string) => {
  // UI calls the control‑plane API to update the session branch
  fetch(`/api/sessions/${sessionId}/branch`, {
    method: "POST",
    body: JSON.stringify({ branchName: branch })
  });
};

```

### Reducer State Synchronization

The session socket reducer in [`packages/web/src/lib/session-socket/reducer.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/lib/session-socket/reducer.ts) processes the `session_branch` message to maintain synchronized branch state across both the scalar session property and repository-specific entries:

```typescript
// packages/web/src/lib/session-socket/reducer.ts
case "session_branch":
  // keep scalar branchName and repo-specific branchName in sync
  const { branchName, repoOwner, repoName } = message;
  const repositories = prev.repositories?.map((repo, i) =>
    repo.repoOwner === repoOwner && repo.repoName === repoName
      ? { ...repo, branchName }
      : repo
  );
  return {
    ...prev,
    branchName,
    repositories,
    // also update the first repo if it is the default target
    ...(targetIndex === 0 ? { branchName } : {})
  };

```

This ensures that **multi-repository sessions** maintain consistent branch attribution across all configured repositories.

## Prompt Payload Structure and Branch Absence

The prompt schema explicitly excludes branch fields, confirming that branch attribution is not handled per-prompt but rather inherited from session state. In [`packages/shared/src/types/session-api.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/shared/src/types/session-api.ts), the prompt type definition contains only the prompt text:

```typescript
// packages/shared/src/types/session-api.ts
// Defines the prompt schema - no branch field included
prompt: z.string()

```

When the client sends a prompt to the server via the `/sessions/[id]/prompt` route defined in `packages/web/src/app/api/sessions/[id]/prompt/route.ts`, the request body contains only the prompt text without any Git metadata:

```typescript
// packages/web/src/app/api/sessions/[id]/prompt/route.ts
const response = await controlPlaneFetch(`/sessions/${sessionId}/prompt`, {
  method: "POST",
  body: JSON.stringify({ prompt: body.prompt })
});

```

## How Agents Access Branch Information

Downstream agents retrieve the branch from the session state when executing operations such as code checkout, image building, or file fetching. The SCM utilities in [`packages/web/src/lib/scm.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/lib/scm.ts) demonstrate how the system generates URLs using the session-stored branch:

```typescript
// packages/web/src/lib/scm.ts
export function getScmBranchUrl(owner: string, name: string, branch: string): string {
  const encodedBranch = encodeURIComponent(branch);
  return `https://github.com/${owner}/${name}/tree/${encodedBranch}`;
}

```

This approach ensures that **all prompts within a session** automatically execute against the same branch unless the user explicitly initiates a branch change through the WebSocket message protocol.

## Summary

- **Session-level attribution**: Git branches are stored in session state via `session_branch` WebSocket messages, not in individual prompt payloads.
- **State synchronization**: The reducer in [`packages/web/src/lib/session-socket/reducer.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/lib/session-socket/reducer.ts) updates both scalar and repository-specific `branchName` properties to maintain consistency across multi-repo sessions.
- **Prompt inheritance**: Prompts defined in [`packages/shared/src/types/session-api.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/shared/src/types/session-api.ts) carry only text content, inheriting branch context from the session state at execution time.
- **Agent consumption**: Background agents read the `branchName` from session state when performing SCM operations, ensuring consistent execution across all prompts in the session.

## Frequently Asked Questions

### Does each prompt in Background Agents carry its own Git branch metadata?

No. According to the source code in [`packages/shared/src/types/session-api.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/shared/src/types/session-api.ts), the prompt schema contains only a `prompt: z.string()` field. Branch information is stored at the session level in the reducer state and inherited by all prompts within that session.

### How does the UI communicate branch changes to the backend?

The web interface sends a `POST` request to `/api/sessions/${sessionId}/branch` with the new branch name, which ultimately triggers a `session_branch` WebSocket message. The reducer in [`packages/web/src/lib/session-socket/reducer.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/lib/session-socket/reducer.ts) handles this message to update the session state.

### What happens to branch attribution when working with multiple repositories?

The reducer synchronizes the `branchName` across all entries in the `state.repositories` array. When a branch change occurs, it updates both the scalar `branchName` property and the specific repository entry, ensuring that multi-repo sessions maintain consistent branch attribution across all configured repositories.

### Where do agents retrieve the branch information when executing prompts?

Agents read the `branchName` directly from the session state object. The SCM utilities in [`packages/web/src/lib/scm.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/lib/scm.ts) use this value to generate provider-specific URLs and perform checkout operations, rather than extracting branch data from the prompt payload itself.