backlog-mcp is an open-source context-engineering backlog for AI agents — tasks, memory, and search, served over MCP; nisli is the zero-dependency UI framework born inside it. This is the story of their first 117 pull requests.

§ 1 — December: The Pain

Building the tool I couldn't buy

backlog-mcp exists because of a personal pain. I work with AI agents every day, and every task manager I could reach was built for teams of humans coordinating with humans. The agent — the thing doing half my engineering — was nobody's user. Plans lived in chat transcripts that would never be read again. Every session started from zero. On the evening of December 19, 2025, I started building the tool I couldn't buy: the first ten commits — schema, storage, a working MCP server — span eighteen minutes in the git log.

This is part 1 of the backlog-mcp saga — the product story. The hundred pull requests are the signal, not the story: the animation above renders every merged PR by the day it landed, and what those ticks record is a tool and its builder figuring out, in public, what they were actually making. The PRs were the unit of the method — one delegation, one review boundary, one merge — and they were instrumental, until I outgrew them. Or until I quit on rigor and dressed it up as process. This post argues it was the first one. You get to decide.

Three claims in this post are still live. nisli, the framework, has one serious user: me. The memory system's north star — an agent measurably smarter in week 10 than in week 1 — is unmeasured. And the claim that pull requests are scaffolding for agentic work can be wrong in a way I would have to publicly walk back. I have argued before that the engineer's role is expanding into an agentic product engineer. This project is where I test that with my own time.

Here is what the run contains. A storage bet — markdown as truth — that everything still stands on. A transport saga that took seven numbered ADRs to end. One weekend where a rejected React spike became a UI framework of my own. Search and context systems that quietly turned a task tracker into a storage engine for agentic context. And nine PRs that died in review, which turn out to matter as much as the ones that merged.


§ 2 — January: The Bet, and Meeting the User

Markdown as truth, and the agent as a user with psychology

The launch burst — ten PRs on January 16 — wired CI, npm publishing, and a web viewer around the prototype. But the decision that shaped everything came in PR #3: storage moved from JSON to individual markdown files with YAML frontmatter. Tasks became human-readable, git-diffable, agent-editable. Nobody has reversed that decision. Everything now stands on it.

A week later came the decision I would defend in front of a protocol committee. An agent editing a task through MCP had to read the whole object and write the whole object back. The first ADR in the repo counts the cost: 10,000+ tokens for a one-line edit, a hundred times what the same agent pays to edit a local file. PR #23 shipped write_resource — surgical operations (str_replace, insert, append) over mcp:// URIs — deliberately mirroring the edit contract Claude is trained on: unique match or loud failure. Six months later, an ADR proposing a CRDT storage backend kept that tool surface untouched, "so the model stays in its trained edit distribution."

"This isn't just about backlog-mcp — it's about establishing a universal pattern for the entire MCP ecosystem." — ADR 0001, "Writable Resources Design", January 2026

Then the tool started teaching me. Late January, a triage agent kept producing malformed reference arrays because it skipped the read-before-write step — LLMs optimize for minimal tool calls, and re-fetching feels redundant to them. The conventional diagnosis: the agent misbehaved. The diagnosis I wrote down instead became the mental model the whole product is built on:

"This is not a bug in agent behavior — it's a UX problem with the API design." — ADR 0037, "Partial Array Updates", January 2026

So the API grew add_references / remove_references — operations matching the agent's mental model of "add to," not "replace all." Its sibling ADR attacked my failure mode rather than the agent's: commitment hoarding. Fifteen open epics, 69 open tasks, 6.7% attention per epic — so the system got decay warnings and grooming pressure, on the theory that "the skill needed isn't better productivity — it's killing good ideas so great ideas can live." The agent is a user with predictable psychology. So am I. Designing for both is what this product turned out to be about — though I didn't know that yet. First, the tool had to survive two walls.


§ 3 — The First Wall: Transport

Seven ADRs to move one protocol

The first wall looks mundane: turn a stdio MCP server into an HTTP server without breaking anyone's config. It ate the last weekend of January — nine PRs in two days — and left a numbered trail of failure, ADR 0013.1 through 0013.7.

The dead ends, in order:

  • A custom stdio-to-HTTP bridge built on the MCP Client SDK. It "added a protocol layer instead of being a transparent proxy" and blocked during initialization. Abandoned.
  • Migrating to StreamableHTTP, the SDK's successor transport. Blocked: mcp-remote, the bridge every MCP client actually uses, didn't support it. Rejected.
  • A dual-mode server serving both transports. Complexity without benefit. Rejected.

The fix that held is a single flag: --transport http-only on mcp-remote. Weeks of architecture, resolved by reading a bridge's options more carefully. I was annoyed. The annoyance was not the waste — dead ends are tuition — it was that months later the question "do I still need mcp-remote?" kept coming back, and every time it felt new. ADR 0013.7 exists for exactly that reason, and its stated purpose is the most honest sentence in the decision log:

"Each time it feels novel because the reasoning lived only in conversation, never on disk. This ADR captures the framework so the loop stops reopening." — ADR 0013.7, June 2026
The agentic lesson

Agents lose context between sessions. So do humans between months. The ADR trail is not documentation — it is memory infrastructure shared by every future session, human or agent. A decision that lives in a chat transcript gets re-litigated. A decision on disk, with its dead ends recorded, stays decided.

The transport wall cost a weekend and taught a process. The second wall cost more, and it is the reason this saga has a framework in it.


§ 4 — The Second Wall: The Framework Weekend

Four days from React spike to nisli

You do not write your own UI framework. It is the one piece of advice the entire industry agrees on, and for good reason: the graveyard is full, and React is right there.

By early February the viewer is fifteen-plus raw HTMLElement classes, each one nuking its DOM subtree with innerHTML on every state change. Scroll position lost. Focus lost. Listeners leaking on disconnect. I was facing challenges in the UI daily, and the honest version of what happened next is the one I typed into the prompt that produced this post: I had no other choice. I ran a spike with React and didn't like it. No other framework aligned with my vision.

The git log records the four days with timestamps:

  • Thursday, Feb 5 — commit 05aca37 adds a React/Vercel best-practices skill to the agent configuration. Thousands of lines of React guidance. The spike is on.
  • Saturday, Feb 7, 21:19 UTC — instead of React code, the first framework ADR appears: signals, targeted DOM patching, three architectures scored against each other.
  • Sunday, Feb 8 — seven ADR revisions. Signal ergonomics settle (.value, not calls). Factory composition. A "Why Not Lit?" section.
  • Monday, Feb 9, 05:32 UTC — PR #71: the framework, implemented. Seven modules, 124 tests passing. By end of day, ten PRs merged and the first components migrated.
  • Same day, 13:18 UTC — commit 0b4415d: remove react skill. PR #76's body reads: "deleted unused vercel-react-best-practices skill (~6100 lines)."

This is the gold cluster in the animation at the top of the page.

The React spike died without shipping a single React line. Let me be fair to the loser, because React would have worked. It is better at almost everything — ecosystem, tooling, hiring, an answer on StackOverflow for every error you will ever see. None of that was what I needed. I needed a UI layer that agents write correctly on the first pass, that I can hold in my head whole, and that adds zero dependencies to a tool people run with npx. Lit came closest, and the ADR records why it lost: "Lit's template engine is battle-tested across millions of users, but our scope (16 components, known use cases) doesn't justify that complexity."

The framework becomes @nisli/corenisli, ნისლი, Georgian for "fog": light, present, barely there. Its design principles read like nothing else in the framework space because the audience is different. This is a framework designed for human–AI coherence:

  • No invented syntax. Standard TypeScript and HTML. No DSL, no compiler. An LLM produces a complete, correct component in one function.
  • Hard to write wrong. Factory props require Signal<T> — lost reactivity is a compile error, not a stale-data bug you find in production.
  • Internals maintainable by AI. "Framework code should read like a tutorial, not code golf." Named functions. Aggressive why-comments in the binding engine.

Under the hood: 2,564 lines of implementation, zero dependencies. A push-pull signal graph that uses a global epoch counter instead of timestamps. A Proxy that materializes prop signals lazily, bridging TypeScript's erased types to runtime reactivity with no schema boilerplate. An effect guard that catches infinite loops — 100 re-runs inside a 2-second window — and disposes the offender with a readable error instead of freezing the tab. And my favorite, from ADR 0069: a character-level state machine in the template parser that tracks am I inside a tag? inside a quote? so expression markers in unquoted attribute positions get auto-quoted —

// template.ts — the auto-quoting state machine (abridged)
// Slots become <!--bk-N--> comment markers; the '>' in '-->' would
// close the tag if the expression sits in an unquoted attribute.

const needsQuotes = inTag && !quoteChar && /=\s*$/.test(s);
htmlStr += needsQuotes ? `"${createMarker(i)}"` : createMarker(i);

Nineteen call sites were already silently broken before that fix. A framework you own is a framework whose bugs you can reach.

The costs are real and written down in nisli's own ADRs rather than discovered by angry users. The template cache is declared and never populated — every mount re-parses. There is no structural diffing of slots; a changed template tears down and rebuilds. when() evaluates its condition once, a documented gotcha. This is what a weekend framework honestly costs.

The loop

Two weeks later, PR #102 extracts nisli as a public npm package. The blog you are reading is built with it. The animated preamble at the top of this post — 108 real merge ticks — is rendered by the framework whose birth those ticks record. The tool became the medium for its own story.

The framework weekend closes at 26 PRs merged in one week — +33,975 lines, −11,807 — with 14 of 14 components migrated and 508 tests passing. If that sounds impossible for one person, that is the point. I didn't type most of it. I made every decision in it. What that division of labor costs is a story I've told before, in how ghx was born — where a "simple" CLI took 23 agent sessions and three rewrites. With both walls down, the tool could finally start becoming what it was going to be.


§ 5 — February: The Becoming

The tool discovers what it is

Mid-February the project turns from plumbing to intelligence, and this is where it stops being a task tracker — not by plan, but by following the agent's actual needs one PR at a time.

Search first. It goes from string matching to Orama BM25, to hybrid retrieval with local embeddings — transformers.js and its 23MB model won over TensorFlow.js and its 150MB of native-compilation misery — to rank fusion. Then it collapses under its own layers: three scoring systems fighting each other, and a query for "feature store" ranking the task literally about FeatureStore at position 18. The diagnostic ADR finds the smoking gun in the normalization: "MinMax normalization annihilates low-BM25 documents: when a relevant document has the lowest BM25 score, it maps to exactly 0.0, making it invisible despite being relevant." The redesign — independent BM25 and vector retrievers, fused linearly, the same place Elasticsearch landed on its own journey — replaces an 852-line service that had accreted ten responsibilities.

Then context. An agent starting work on a task needed 5–10 sequential MCP calls to assemble its picture — parent epic, siblings, resources, recent operations. Call it 4,000 tokens of plumbing before any real work. PR #90 ships backlog_context: one call, one structured bundle, semantic search folded in so the agent discovers related work it didn't know to ask about. And in the ADR behind that tool, the product says out loud, for the first time, what it is becoming: "backlog-mcp is evolving from a task tracker into a context engineering platform — a second brain for humans and AI agents working together."

I did not set out to build that sentence. I set out to stop losing plans in chat transcripts. But every fix for the agent's real needs — surgical edits, partial updates, one-call context — pointed the same direction, and by late February the direction had a name. These are efficiency claims, and efficiency claims about agent tooling deserve measurement, not vibes; I turned that exact suspicion on my own tools in a later post about evals, and backlog-mcp's context pipeline is on that docket too. Frustration is not proof, and neither is a demo.


§ 6 — April: The Graduation

The review moves upstream

Pull requests are how serious software gets built. I ran 117 of them believing that, and the belief was correct — the project's entire shape was negotiated inside those PRs. In the early months the PR was the delegation boundary: I brief agents, they produce a branch, and the diff is where I audit work I did not type.

The nine missing numbers prove the boundary had teeth. 117 PRs opened, 108 merged — and the other nine read like echoes of their neighbors. #38, "Complete Fastify migration," closed; #37, the same migration, merged. #53 and #54 are both the copy-button system; #55 merged it. #45, #46, and #47 are three runs at the same resource refactor. Those are agent branches I rejected and had redone — sometimes twice — before anything reached main. A review boundary that never kills anything is a ritual. This one killed roughly one attempt in thirteen.

April 3 is the last PR day — eight merges wiring GitHub OAuth and ChatGPT compliance — and then the stream goes quiet. Not the project. The commits continue to this day, straight to main. What ended was the pull request as my unit of work, and it ended because three things matured at once:

  • Observability arrived. The activity panel and operation log — built inside these very PRs — attribute, diff, and expose every agent write after the fact. A later ADR names the op-log "the canonical write journal: the single record of how state arrived at its current shape."
  • Review moved to the decision. The process hardened into ADR-first work: delegation briefs with exact scope, step-0 audits before edits — one rename classified ~262 references as change-or-leave before touching a file — engineering records after. By the time code exists, the risky thinking has already been reviewed.
  • The overhead inverted. For a solo maintainer whose collaborators are agents, branch–PR–merge ceremony added latency without adding safety the ADR trail and op-log weren't already providing.
The pull request didn't fail. It graduated — from the place where work gets reviewed to the scaffolding that taught the process to review itself. — the claim this post exists to make

This does not generalize to teams, and I would re-adopt PRs the day a second human joins. But for agentic solo work I now believe the natural review boundary is the decision, not the diff. The diff is an implementation detail of a decision that should already be on disk. That is a falsifiable position — if the direct-commit era produces a regression an old-style PR review would have caught, I am wrong, and the op-log will show it.


§ 7 — The Horizon

From task tracker to storage engine for agentic context

The positioning ADR preserves its own three-stage evolution on purpose, so a newcomer sees all of it at once: the product is "a task tracker today, an agentic storage engine by architecture, and a data bank for the full agentic toolchain in its trajectory." The architecture was type-agnostic from the start — storage iterates any markdown entity, the viewer renders any frontmatter. The framing took months to catch up. When it did, it produced the boldest sentence in the repo:

"backlog-mcp is to agentic content what git is to source code and what Postgres is to relational data — the smallest, most reusable primitive that the domain-specific tooling sits on top of." — ADR 0097, "Agentic Storage Engine Positioning", April 2026

The post-PR era is being spent cashing that claim. Three moves define it.

Memory. The largest arc since the PRs stopped — fifteen sub-ADRs — begins with a survey of 40+ agentic memory systems and one sharp observation about the incumbents: Mem0 sends an LLM's structured output to another LLM to "extract memories." But the agent is an LLM. A second LLM re-extracting what the first one wrote is redundant. The breakthrough is noticing there is nothing to build: "the backlog IS the memory." Tasks, evidence, ADRs, operations — that is episodic memory already. The problem is surfacing, not storage. So memory becomes four verbs — wakeup, recall, remember, forget — over the existing substrate, no LLM in the write path. A year later the field converges on the same append-leaning, deterministic design. The north star is the most testable sentence in the decision log: an agent using backlog-mcp should be measurably smarter in week 10 than in week 1, on the same project, because of memory. It is unmeasured. It stays in this post anyway, because declared targets you can miss are the only ones worth declaring.

Semantic intent at the boundary. The unified entity model that keeps the backend sane leaked into the tool surface — backlog_create(type=memory) forces the agent to think like the persistence layer. The current direction inverts it: the MCP surface speaks the agent's intent language (remember, recall, schedule), and the substrate abstraction stays internal. Hexagonal architecture, applied to a port whose consumer is a language model — made affordable by deferred tool loading, which turned "another tool" from a context tax into a one-line name.

Local-first, deliberately. The Cloudflare Workers + D1 cloud mode — those final OAuth PRs — got demoted in June to a "constrained satellite." Feature parity with the weakest environment was dragging every design toward the lowest common denominator, and the capabilities that matter — local embeddings, hybrid search, memory — live on the local side. The next act is already written as a proposal: Loro, a CRDT, as the history substrate. The op-log plus a manual git habit is "two half-built history systems." Agents as CRDT peers is how single-writer today becomes multi-agent tomorrow without conflict copies.


§ 8 — The Tally

What one hundred pull requests buy

Not the software — the practice. Decisions on disk. Dead ends numbered instead of deleted. APIs written for the psychology of the model on the other side. Scaffolding held exactly as long as it earns its overhead, and dropped the day it doesn't.

And a texture no changelog records. The framework ADR lands at 21:19 UTC on a Saturday. The implementation lands at 05:32 UTC on a Monday, and ten merges follow before that day ends. Nobody assigned any of this. No sprint, no standup, no team. Building in the open, alone with agents, looks exactly like the chart at the top of this page — bursts where the determination spiked, silence where the thinking got long, and not one tick placed by anyone but me.

108
merged PRs, numbered 1–117
+97,602
lines added, −25,639 deleted
137
architecture decision records
78
days, Jan 16 → Apr 3, 2026

The numbers are the consequence. The saga is the point, and it is not over. Part 2 of this series is nisli's internals — benchmarks and the unflattering parts included. Part 3 is the memory eval: whether week 10 actually beats week 1. I want people to find this project, and I want them to find me — that want is typed verbatim into the raw prompt behind this post, on the prompts page.

108 merges. 137 recorded decisions. One framework. The claims are live and at least one of them can come back wrong.

Tell me which one — I'm @GogaKoreli.

① backlog-mcp — github.com/gkoreli/backlog-mcp — the repo, including all 137 ADRs under docs/adr/ (Jan–Jun 2026)
② nisli (@nisli/core) — github.com/gkoreli/nisli · npm — zero-dependency reactive web component framework (Feb 2026)
③ Model Context Protocol — modelcontextprotocol.io — Anthropic's open protocol for agent–tool integration (2024)
④ Orama — github.com/oramasearch/orama — TypeScript-native BM25 + vector search (evaluated Jan 2026, ADR 0038/0049)
⑤ transformers.js — github.com/huggingface/transformers.js — local embeddings in the hybrid search stack (ADR 0042, Feb 2026)
⑥ mcp-remote — github.com/geelen/mcp-remote — the stdio↔HTTP bridge at the center of the transport saga (ADR 0013.x)
⑦ Loro — github.com/loro-dev/loro — CRDT library proposed as backlog-mcp's history substrate (ADR 0107, Jun 2026)
⑧ PR merge data — GitHub API, gh pr list --state merged on gkoreli/backlog-mcp; 117 PRs opened, 108 merged. The other nine (#30, #31, #35, #38, #45–47, #53, #54) were closed unmerged — rejected attempts superseded by a redo. The preamble renders the merged set verbatim.