Most backlogs have the same problem. The answer to "why did we close that bug?" or "who was working on the Safari payment issue?" is sitting in there somewhere, in a comment from three sprints ago. Nobody finds it. People just ask in Slack and wait.
I wanted to be able to ask an agent instead. The catch is that LLMs don't remember anything between calls, so the agent needs a memory it can actually search. That's what I built for Mergetober: a connector that syncs Azure DevOps Boards into Cognee.
I picked this issue because it came paired with an Azure Repos one, and building both meant they could link up: a bug on the board, and the pull request that fixed it, in the same graph. Azure DevOps is also where a lot of teams outside the GitHub world keep their backlog, and Cognee didn't have a connector for it yet.
Cognee in one paragraph
Cognee is an open source memory layer for AI agents. You hand it documents, chats or data, it builds a knowledge graph of the people, things and decisions in there and how they connect, and an agent can query that graph later. The whole API is basically three calls: add, cognify and search. A connector is just something that feeds add from a tool your team already uses.
What the connector does
It pulls every work item from one Azure DevOps project and turns each one into a text document: the fields that matter, the links to other work items and pull requests, the description, and the full comment thread. Here's what a bug looks like after the connector renders it:
# Bug #412: Card payments fail on Safari 17
Type: Bug
State: Active
Board column: Doing (Payments team / Stories)
Assigned to: Priya Nair
Area: Shop\Payments
Iteration: Shop\Sprint 14
Priority: 1
Parent: work item #398
Pull requests: pull request !87
Repro steps:
Open checkout on Safari 17
Pay with a saved card that needs 3DS
Comments:
- Ravi Kumar on 2026-10-02: Only happens when 3DS is triggered.
Email addresses never go in, only display names. And System.Rev and ChangedDate are left out on purpose, so the text only changes when something a human would notice changes. That matters because Cognee skips documents whose content hash didn't move.
Using it is one call:
import cognee
from cognee_community_connector_azure_devops_boards import azure_devops_boards_source
await cognee.remember(
azure_devops_boards_source(organization="my-org", project="my-project"),
dataset_name="my_backlog",
write_disposition="merge",
)
answer = await cognee.search(
query_text="Which open bugs are blocking the payments release?",
datasets=["my_backlog"],
)
You need a personal access token with Work Items (Read). That's it.
How a sync works
Run it again tomorrow and it only fetches what changed. Delete a work item in Azure DevOps and it disappears from memory on the next run. Getting those two things right took most of the time, and a few parts weren't what I expected.
Things I learned building it
The obvious query breaks on real projects
My first idea was a WIQL query like "give me everything where ChangedDate is after the last sync". It works on a demo project. On a real one, WIQL refuses outright once a query matches more than 20,000 items (error VS402337), and the very first sync matches everything. Azure DevOps has a reporting revisions feed made for exactly this. It returns a continuationToken that acts as a server-side bookmark, so the connector stores that and asks "what changed since this token" next time. No clock to drift, no 20k cap.
Deleted and destroyed are two different things
A deleted work item goes to the recycle bin, and the revisions feed tells you about it. But if someone empties the recycle bin, that item is just gone, with no trace in any feed. So every run also asks WIQL which ids still exist, paged by id range to stay under the 20k limit, and anything we had before that's missing becomes a delete.
That check has one guard I'm glad I added. If it comes back empty while we know about hundreds of items, the connector deletes nothing. A blip in the API is far more likely than a project that emptied overnight, and wiping someone's entire memory because of a timeout would be the worst possible bug.
Azure DevOps warns you before it throttles you
It sends a Retry-After header on normal 200 responses when you're getting close to the limit, not only on 429 errors. Most retry code only looks at 429s. The client waits whenever it sees the header, whatever the status code.
One argument decides whether your data survives
You have to pass write_disposition="merge". Cognee's default is "replace", and since the connector only sends what changed, replace would make Cognee forget every work item that didn't change that day. The README and the example both call it out, because it's an easy way to lose a whole backlog.
A self-review caught a real bug
Before opening the PR I went through the code the way a maintainer would. It turned out every project shared one resource name, so syncing a second project would have reused the first project's cursor and could have deleted its items. Each project now gets its own name and pipeline scope, the same way Cognee's own connectors do it.
The cursor bug is the one that stuck with me. Every test was green, and it only showed up when I asked a simple question: what happens if someone syncs two projects? Next time I'll ask those questions before writing the tests, not after.
Testing it
There are 32 unit tests that run against a faked Azure DevOps API. They cover the cursor, batching, comments, every delete path, the empty-sweep guard and the retry logic. One more runs the connector through the real cognee.add() pipeline on Cognee 1.6.3 and checks that deleted and destroyed work items are actually gone from Cognee's data, an edited one is re-ingested, and an untouched one keeps its exact id.
There's also a read-only live test that runs against a real project once you set AZURE_DEVOPS_PAT, AZURE_DEVOPS_ORG and AZURE_DEVOPS_PROJECT. If you have an org, I'd love to hear how it does on yours.
What's next
I'm also building the Azure Repos connector for pull requests. Both connectors write work items as work item #412 and pull requests as pull request !87, which is how Azure DevOps refers to them. So with both synced, the graph connects a bug to the PR that fixed it and to what the reviewers said on that PR. That's the question I actually want answered: not just "what's this bug" but "how did we fix it, and why that way".
Links:
- Pull request: topoteretes/cognee-community#300
- Issue: topoteretes/cognee#4708
- Cognee: github.com/topoteretes/cognee
- Mergetober: wemakedevs.org/hackathons/mergetober


Top comments (0)