Goatfied

devex

Branch Aware Context Retrieval Pipelines Implementation Guide: practical systems guide

Technical field guide on branch aware context retrieval pipelines implementation guide for teams building dependable AI coding workflows.

2026-07-208 min readBy Goatfied
Engineering team collaborating on developer experience

Most AI coding assistants retrieve context naively—they grab whatever's in your current file or workspace, ignoring the fact that your feature branch diverged from `main` three days ago and now references functions that don't exist in the base branch. When the agent suggests code that compiles locally but breaks in CI because it assumes dependencies you added but haven't merged yet, you've hit the branch-awareness gap.

Branch-aware context retrieval solves this by treating your repository's branch structure as a first-class constraint in the retrieval pipeline. Instead of indexing the entire workspace once, you maintain separate embeddings or metadata per branch, merge base, or even per-commit snapshot, and query against the correct context for the task at hand. If you're reviewing a PR, the retriever pulls context from the target branch. If you're coding a feature, it uses your current HEAD. If you're generating migration scripts, it diffs both.

This guide walks through building a branch-aware retrieval system for code assistance—covering branch context isolation, incremental index updates, query-time branch resolution, and practical tradeoffs when you need both speed and correctness.

Why branch awareness matters for code retrieval

Standard RAG pipelines for code treat the repository as a single, static knowledge base. You chunk functions and docstrings, embed them, store vectors in Pinecone or Weaviate, and retrieve on similarity. This works until:

  • **A developer is on a feature branch** and the agent retrieves examples from `main` that contradict newly introduced abstractions.
  • **A PR review agent** suggests changes based on outdated context because the index hasn't refreshed since the branch was created.
  • **You're refactoring across branches** and need to see which branches would be affected by a signature change in `main`.

Branch-aware retrieval means your pipeline knows which branch context to use for each query. When an agent plans edits, it retrieves only from the current branch's committed state plus staged changes. When reviewing, it diffs the PR head against the base branch and retrieves from both sides to surface conflicts or redundant code.

Goatfied's agent loop depends on this: the **plan** step retrieves relevant functions and types from the correct branch, the **constrain** step checks them against compile/lint rules in that branch's config, and the **validate** step runs tests in the branch's exact dependency tree. Without branch isolation, the agent would propose changes that compile in one context but fail in another.

Structuring branch-isolated context stores

The simplest approach: maintain one vector index per active branch. When a developer checks out `feat/new-api`, your indexing service:

1. Detects the branch change via a Git hook or file watcher.

2. Generates a branch-specific namespace in your vector DB (e.g., `repo:myapp/branch:feat-new-api`).

3. Chunks and embeds the code at that branch's HEAD.

4. Stores vectors with metadata: `{branch: "feat/new-api", commit_sha: "abc123", file_path: "src/api.ts"}`.

Queries then include a branch filter. When the agent needs context for an edit, it scopes retrieval:


results = vector_db.query(

    embedding=embed(user_query),

    filter={"branch": current_branch, "repo": repo_name},

    top_k=10

)

**Tradeoff**: Full per-branch indexes scale poorly if you have hundreds of short-lived branches. For large teams, consider:

  • **Base + delta indexing**: Index `main` fully, then store only diffs for feature branches. At query time, merge base results with branch-specific deltas.
  • **Lazy indexing**: Index a branch only when a query targets it. Cache the index for reuse if the branch is active.

Either way, you need a strategy for pruning stale branches—drop indexes for branches merged or deleted more than N days ago.

Incremental updates on branch mutations

Reindexing the entire branch on every commit is expensive. Instead, track file-level changes:

  • Hook into Git's post-commit or post-merge events.
  • Diff the commit: extract added, modified, deleted files.
  • Re-chunk and re-embed only the changed files.
  • Upsert new vectors, delete vectors for removed code.

Example flow:


# In .git/hooks/post-commit

git diff --name-only HEAD~1 HEAD | while read file; do

  if [[ "$file" =~ \.(ts|py|go)$ ]]; then

    curl -X POST http://indexer:8080/update \

      -d "{\"repo\":\"myapp\",\"branch\":\"$(git branch --show-current)\",\"file\":\"$file\"}"

  fi

done

Your indexer service fetches the file content, chunks it (say, by function or class), embeds with a code model (e.g., `text-embedding-3-large` fine-tuned on code, or a dedicated model like StarCoder embeddings), and writes to the branch-namespaced index.

**Chunking tip**: For branch-aware retrieval, include the function signature and its immediate callers in the chunk metadata. This helps the retriever surface not just the function definition but also usage examples from the same branch—critical when the API changed mid-branch.

Query-time branch resolution

When the agent processes a user request, it needs to determine which branch context to use. Common patterns:

  • **Explicit branch parameter**: User or IDE extension specifies the branch. If you're in VSCode on `feat/auth`, the extension passes `branch=feat/auth` in the context request.
  • **Implicit from Git state**: Your backend runs `git rev-parse --abbrev-ref HEAD` in the workspace and uses that branch.
  • **Multi-branch queries**: For PR reviews, query both the PR head and base branch, then intersect or diff results. Show the agent "here's what exists in `main`, here's what's new in this branch."

Goatfied's approach: when the agent enters the **plan** phase, it reads the branch from the workspace metadata (stored in `.goatfied/state.json` or similar) and retrieves from that branch's index. If you're comparing two branches (e.g., generating a migration script), the agent runs two separate retrievals and passes both contexts to the LLM:


base_ctx = retrieve(query, branch="main")

head_ctx = retrieve(query, branch="feat/refactor")

prompt = f"Base branch has: {base_ctx}\nFeature branch has: {head_ctx}\nGenerate migration..."

This dual-context pattern is essential for detecting breaking changes—if a function signature in `head_ctx` differs from `base_ctx`, the agent can warn or suggest compatibility shims.

Handling merge conflicts in retrieval

Merge conflicts aren't just a Git problem—they're a retrieval problem. If `feat/a` and `feat/b` both modified `auth.ts` in conflicting ways, and you're trying to merge `feat/a` into `main` while `feat/b` is still open, the agent needs to retrieve context from the *potential merge state*, not just the individual branches.

One technique:

1. When a merge or rebase starts, compute the three-way merge base.

2. Index the merge base commit separately.

3. At query time, retrieve from the merge base plus each branch's deltas.

4. Use the LLM to synthesize context: "Merge base had X, branch A changed to Y, branch B changed to Z—here's how to reconcile."

This is advanced, but necessary if you're building agents that autonomously resolve merge conflicts or suggest conflict-free refactorings.

Practical architecture: self-hosted vs. managed

If you're running Goatfied self-hosted, you control the entire pipeline. Deploy an indexer service that hooks into your Git server (GitHub, GitLab, Bitbucket), subscribes to webhook events for pushes and PR updates, and maintains a vector DB (Qdrant or pgvector) scoped by branch. Your agent queries the indexer API directly.

If you're using a managed platform, check whether it supports branch namespacing. Some services index only the default branch; you'll need to fork or extend them to handle feature branches. Alternatively, run a lightweight sidecar indexer that syncs with the managed platform's API.

**Cost consideration**: Embedding every commit on every branch adds up. A 100-contributor team with 50 active branches and 10 commits/day/branch = 500 embed operations daily. At ~$0.0001 per 1K tokens, and assuming 500 tokens/commit average, that's ~$0.025/day or ~$9/year per repo—negligible, but multiply by 100 repos and it's worth batching or rate-limiting.

  • [Goatfied vs Cursor vs GitHub Copilot: a benchmark across 50 real PR tasks](/blog/goatfied-vs-cursor-vs-github-copilot-benchmark-50-pr-tasks)
  • [The Goatfied agent loop: how we ship code that actually compiles first try](/blog/goatfied-agent-loop-compiles-first-try)

Related posts

Branch Aware Context Retrieval Pipelines Implementation Guide: practical systems guide | Goatfied Blog