<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title>Epiq blog</title>
		<link>https://epiq.sh/blog.html</link>
		<atom:link href="https://epiq.sh/blog/feed.xml" rel="self" type="application/rss+xml" />
		<description>Writing from the Epiq project: developer experience, event sourcing, Git internals, and building a terminal-native issue tracker.</description>
		<language>en</language>
		<lastBuildDate>Fri, 30 Oct 2026 00:00:00 GMT</lastBuildDate>
		<item>
			<title>Sandboxes favor repo-native tooling</title>
			<link>https://epiq.sh/blog/repo-native-tooling-for-sandboxes.html</link>
			<guid isPermaLink="false">https://ljtn.github.io/epiq/blog/repo-native-tooling-for-sandboxes.html</guid>
			<pubDate>Fri, 30 Oct 2026 00:00:00 GMT</pubDate>
			<dc:creator>Jonatan Lampa</dc:creator>
			<category>ai</category>
			<category>agents</category>
			<category>security</category>
			<description>Recent incidents possibly involving rogue agents highlight the importance of sandboxing.</description>
			<content:encoded><![CDATA[<p><img src="https://epiq.sh/blog/images/repo-native-tooling-for-sandboxes.webp" alt="Out-of-focus lines of code glowing blue and amber on a dark screen" /></p>
<p>Recent incidents <a class="inline-link" href="https://www.euronews.com/2026/10/14/entire-swedish-reality-show-season-lost-weeks-before-premiere-amid-disputed-ai-claims" rel="noopener">possibly involving rogue agents</a> highlight the importance of sandboxing. A common setup is a privileged gatekeeper agent on your host machine whose sole responsibility is to delegate work to restricted peers in sandboxed containers.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/sandbox-gatekeeper-simple.svg" alt="Every interaction with the gatekeeper is an opening in the wall." loading="lazy" decoding="async" /><figcaption>Every interaction with the gatekeeper is an opening in the wall.</figcaption></figure><p>This is not watertight, and as the saying goes: if there is a way in, there is a way out, as we shall soon see. In any case, it is better than nothing at all, and most importantly it narrows the contact surface with the outside world.</p><h2>It's political</h2><p>It also makes interactions with the sandboxed agents a pain. Agents without a way to communicate with peers, the gatekeeper, or humans are impractical. The common solution is to relax restrictions with tooling whitelists. This is not only an annoyance from a technical standpoint. In a large corporation, it takes real political skill to navigate security departments and corporate machinery.</p><h2>Repo-native tooling</h2><p>A way around this is repo-native tooling. Tools that work offline with only Git access have most of the allowances in place, as agents need Git anyway to deliver code.</p><p>If, for instance, the control plane is part of the repository, we need fewer gates to the outside. We can use Git as a communication transport and a plain log in a repository as memory persistence.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/sandbox-repo-native.svg" alt="What crosses the wall is Git, already allowed" loading="lazy" decoding="async" /><figcaption>What crosses the wall is Git, already allowed</figcaption></figure><h2>Trusted channels are soft spots</h2><p>Now, the sandbox itself is typically not what fails, but <a class="inline-link" href="https://labs.cloudsecurityalliance.org/research/csa-research-note-ai-coding-agent-sandbox-escapes-20260722-c/" rel="noopener">the doors left open on purpose</a>. Think of it as a drug lord in prison - the cartel is just as lethal as long as he can slip out notes to someone able to act on them, no matter how restricted he is.</p><p>Repo-native, Git-based tooling narrows the contact surface with the outside, but it does not limit information flow. An important question is who is reached by what information, with what privileges.</p><h2>The Lethal Trifecta</h2><p>The combination of <em>private data</em>, <em>a way out</em>, and <em>untrusted content</em> is called <a class="inline-link" href="https://cosmonic.com/ai-sandbox/principles/lethal-trifecta/" rel="noopener">the Lethal Trifecta</a>. We can contain risk by cutting off one of its three legs.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/lethal-trifecta.svg" alt="Remove one leg, and the circuit breaks." loading="lazy" decoding="async" /><figcaption>Remove one leg, and the circuit breaks.</figcaption></figure><p>Sandboxed agents never see secrets, so they lack private data. The gatekeeper does hold secrets, has a way out, and reads what sandboxed agents write, so it is supervised by a human, runs in manual mode and never executes commands.</p><h2>Dead drop</h2><p>Separating the workflow repository from the code repositories keeps the host away from code writes. <a class="inline-link" href="https://epiq.sh/" rel="noopener">Epiq</a> is an issue tracker and workflow manager that can operate as the control plane in this way.</p><p>It persists an event log in your repo and can track work in other repositories, from a repository with or without a remote, online or offline.</p><p>The gatekeeper, with no write access to the code, puts notes in the dedicated workflow repo. The notes are picked up by sandboxed agents, with code write access. Think of a Cold War <a class="inline-link" href="https://en.wikipedia.org/wiki/Dead_drop" rel="noopener">dead drop</a>. In return, sandboxed agents drop notes back onto the workflow repository, read as untrusted content, leaving an audit trail behind.</p><h2>Bare clones</h2><p>Git itself can be used in clever ways by attackers. <a class="inline-link" href="https://adversa.ai/blog/top-ai-coding-agent-security-resources-october-2026/" rel="noopener">GitSpawn</a> used repository config to run code whenever Git was called.</p><p>When Epiq links another repository, it reads it through a shallow, bare clone: no working tree, nothing checked out, hooks and <code>.gitattributes</code> switched off. Git only ever transfers content, never settings or hooks, so nothing the sandbox writes to a linked repository can make the host run anything.</p><h2>A recent setup</h2><p>A setup I am currently running looks something like this:</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/sandbox-openshell-setup.svg" alt="The proxy appends real secrets per binary, host and path." loading="lazy" decoding="async" /><figcaption>The proxy appends real secrets per binary, host and path.</figcaption></figure><p>I use <a class="inline-link" href="https://github.com/NVIDIA/OpenShell" rel="noopener">OpenShell</a> to run sandboxed agents, keeping secrets out of the sandbox entirely, since they are appended by a proxy layer on requests leaving the sandbox.</p><p>Epiq works as the control plane in a separate repository, providing a way for agents to interact in a traceable way, and a way for humans to audit agent interactions.</p><p>One future improvement, since the communication is passive (text rather than executed commands), is that we could involve audit agents before information is relayed to agents with privileges.</p><p>Note: Since agents can write to the workflow repo, it's wise to protect its branch against force-pushes and deletion.</p><h2>Composable, sandboxable</h2><p>Building blocks that are repo-native, standalone and distributed allow you to compose a great variety of setups, depending on organizational needs. Epiq can be used as an agent memory graph, a workflow manager, an audit trail, or all at once.</p><p>When I started Epiq a year and a half ago, agents weren't a priority. But an append-only event log that lives in the repo and travels over Git turns out to be what sandboxed agents need: a way to coordinate without opening another gap in the wall.</p><hr /><p>Is a repo-native control plane the right way to run sandboxed agents? I'd like to hear your thoughts, or how you run yours, in the comments.</p>]]></content:encoded>
		</item>
		<item>
			<title>The soft fabric of agentic code</title>
			<link>https://epiq.sh/blog/the-soft-fabric-of-agentic-code.html</link>
			<guid isPermaLink="false">https://ljtn.github.io/epiq/blog/the-art-of-not-writing-all-the-code.html</guid>
			<pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
			<dc:creator>Jonatan Lampa</dc:creator>
			<category>ai</category>
			<category>agents</category>
			<category>architecture</category>
			<category>testing</category>
			<description>Agentic code can be thought of as a fabric. It is easily shaped and can assume any form with little effort, but it is hard to build something rigid out of…</description>
			<content:encoded><![CDATA[<p><img src="https://epiq.sh/blog/images/the-soft-fabric-of-agentic-code.webp" alt="" /></p>
<p>Agentic code can be thought of as a fabric. It is easily shaped and can assume any form with little effort, but it is hard to build something rigid out of it. While agents can produce brilliant snippets of code at the level of human experts, cohesion and consistency remain a challenge.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/fabric-loose.svg" alt="Unguided, agentic code is shapeless, like fabric." loading="lazy" decoding="async" /><figcaption>Unguided, agentic code is shapeless, like fabric.</figcaption></figure><h2>The peg fallacy</h2><p>There is a real problem with the softness of fabric alone. A natural inclination is to address the lack of structure in agentic workflows by adding tests, contracts, static analysis and agent harnesses. However, pinning down the edges with pegs does little to enforce internal structural rigidity. Without an internal structure to apply the constraining forces upon we are still in a soft, flat mess.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/fabric-pegged.svg" alt="Pinned down by tests, but still a mess." loading="lazy" decoding="async" /><figcaption>Pinned down by tests, but still a mess.</figcaption></figure><h2>Building a tipi</h2><p>We'd be helped by sturdy poles to stretch the fabric into its desired shape. In software terms - a distinct architecture by which all additional work is guided and upon which the tests and static analysis apply constraining forces. In a tent, there is a symbiotic relationship between the internal structure, the fabric and the pegs. Software is much the same. Without an architecture to stretch the web of features across, the constraining pegs are of little use. Without the pegs, on the other hand, we might wake up in the morning with all of the wooden structure in place, but the fabric in a tree top far away.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/tipi.svg" alt="Structure and tests together give the soft fabric its shape." loading="lazy" decoding="async" /><figcaption>Structure and tests together give the soft fabric its shape.</figcaption></figure><p>That's the theory. Here is how it played out on my project.</p><h2>A thousand doubts</h2><p>In the early summer of 2025 I started building Epiq, an issue tracker that keeps its state in the repository and syncs over Git.</p><p>Back then agentic workflows were but distant rumors. The architecture was forged in manual struggle with the code, endless rubber ducking, 11h meditation sessions (road trips) and a thousand doubts.</p><p>Trees in the forest of ideas were selected, pruned of false branches, logged, sharpened, and finally knit together. In the end, a few non-negotiable architectural poles emerged:</p><ul><li>User-scoped, append only event logs for persistence</li><li>Events with <a class="inline-link" href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener">CRDT</a>-like properties, only referencing shared history</li><li>Causal order resolution, tie-broken by ULID</li></ul><p>Upon these rests every later feature.</p><h2>Constraints over slop</h2><p>None of these is exotic on its own, but together they are not a mainstream solution. Had the workflow been 100% agentic, this architecture would have been an <em>unlikely</em> outcome, since probabilistic models punish anything unorthodox.</p><p>The solution becomes more likely when revealing the constraints:</p><ul><li>The board lives in the repo with the code (no server, vendor-agnostic)</li><li>Git is the only transport</li><li>Users are never asked to resolve merge conflicts</li><li>Several people, and agents, might edit at the same time, on- or offline</li></ul><p>These constraints were derived from a human vision. Novelty is unlikely when not guided by constraints, and without a vision, no constraints are given.</p><p>Agents lacking a vision for the codebase is why, if you ask for a red button, and then for a blue button, you might get two different button components rather than a single configurable button.</p><h2>Pinning it down</h2><p>Once the poles were in place, post launch in summer 2026, a fabric factory opened its gates a meter away. Enter Claude Code. Introduced any earlier, it might have been to the detriment of the architecture. As it was, the agents arrived at the precise moment when a fabric of features had a frame to stretch across.</p><p>Suddenly not only features were cheap, but tests too. You could add a thousand tent pegs for a single pole, firmly pinning it to the ground. The core is now pegged down with paranoid tests trying to break the concurrency model or the Git interactions. One test simulates a team of 20 using the tracker intensely every day for 10 years, verifying the board is still usable and responsive at scale.</p><p>We live in an age where pegs are cheap, allowing us to lock the architecture firmly in place.</p><h2>The great inversion</h2><p>The observant reader might have noticed that there is a common denominator between all of the foundational poles in this project. They are all concerned with the data model.</p><p>A common notion is that data is soft and software is rigid. Agentic workflows have challenged, one might even say, inverted, this perceived relationship. What has not changed, however, is the cementing effect data has on a system. Data comes with migrations, uncharted sources, varying quality, risky transformations, all of which are petrifying to system evolution.</p><p>Picking the right data model is not an afterthought, but a core feature of the architecture. Poles to stretch the soft tissue of code across, nailed to the ground with tests.</p><p>On this project, the data model has proven not only essential for distributed live collaboration, but has also resulted in an evolvable system. So far every new feature has been added by deriving information from the logs in a new way or by adding another event type, never by migrating existing data. When image attachments were added, for instance, all it took was another event type, which newer clients recognized, and older ones ignored. When advanced statistics were added, it was a matter of identifying derivable insights from the event log.</p><p>Not all systems live under the same constraints, and other visions call for other structural poles. In any event, one can expect the data model to outlive the code, and as such it must be considered a part of the load-bearing structure.</p><h2>The aimless factory</h2><p>A massive amount of effort is going into setting up loops, harnesses and software factories, but little attention seems to be given to intentionality and vision. In the real world there are factories that overproduce to justify their existence because they are static, massive units built for predicted demand that never materialized. And so they produce high-quality garbage that no one wants. This happens in software too, which I can attest to first-hand (but that's another story).</p><p>Sometimes you just need an axe, a few trees and a skilled craftsman. Intention, comprehension and vision beat big, aimless factories.</p><h2>Back to the tipi</h2><p>Agentic workflows can help us wrap up our framing in beautiful ornaments in no time and help us pin it firmly to the ground with a thousand pegs, but without well-understood, sturdy poles of architecture, we have nothing but a soft, formless mess. Ornate, but flat, wet and tangled.</p><p>The art of not writing all the code is <a class="inline-link" href="https://epiq.sh/blog/cost-of-cognitive-debt.html" rel="noopener">knowing which parts you must build intuition for</a>, starting from a vision, deriving the constraints, and raising the poles.</p><p>Does this resonate with you, or do you disagree? Please share your thoughts below.</p>]]></content:encoded>
		</item>
		<item>
			<title>The Cost of Cognitive Debt</title>
			<link>https://epiq.sh/blog/cost-of-cognitive-debt.html</link>
			<guid isPermaLink="false">https://ljtn.github.io/epiq/blog/cost-of-cognitive-debt.html</guid>
			<pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate>
			<dc:creator>Jonatan Lampa</dc:creator>
			<category>ai</category>
			<category>architecture</category>
			<category>ddd</category>
			<category>programming</category>
			<description>It is easy to end up in one of two absolutes: either agentic workflows are the future and mark the end of traditional coding, or, agentic workflows have no…</description>
			<content:encoded><![CDATA[<p><img src="https://epiq.sh/blog/images/cost-of-cognitive-debt.webp" alt="A wide, dark abstract banner" /></p>
<p>It is easy to end up in one of two absolutes: either agentic workflows are the future and mark the end of traditional coding, or, agentic workflows have no place in modern software development whatsoever. However, there may be a third way, one that recognizes the problems, and addresses the risk in a methodical way.</p><p>I'd like to introduce you to <em>Cognitive Debt</em> - the condition where human understanding of a part of your system is degraded - and to the <em>Cost of Cognitive Debt</em>, or <em>CCD</em>, an estimation of what that condition costs in reality.</p><h2>Accumulating unknown unknowns</h2><p>It makes sense that not understanding the inner workings of a system which a company's strategy relies on has real-world costs. You would, for instance, be unable to create a plan for overtaking your competitors without understanding your own technical limitations and advantages.</p><p>Parts of a system not understood by humans could be thought of as subject to Cognitive Debt, distinct from technical debt. Where technical debt describes understood technical problems within a system - Cognitive Debt describes the fact that you don't even understand if you have a problem.</p><p>This is not a new observation. Peter Naur argued in <a class="inline-link" href="https://pages.cs.wisc.edu/~remzi/Naur.pdf" rel="noopener">Programming as Theory Building</a>, back in 1985, that a program is not limited to its source code but a theory held in the minds of the people who built it. Lose the people and you have lost the reasons for why the program exists at all, even though it still compiles. What agentic workflows change is that we can now produce code that never had a theory behind it to begin with.</p><p>It is reasonable to assume that Cognitive Debt eventually can lead to Cognitive Meltdown - a state where no human is able to understand how a system works given any reasonable amount of time. This would render the system useless from a business point of view, while the cost of recovering insight would outweigh any potential gains.</p><h2>A scale of understanding</h2><p>Understanding can range from a simple acknowledgement of the problem a piece of code solves to a deep intuition. While a review may give you a mental note on what a piece of code does, it is often necessary to hand-write the code to acquire the deepest form of understanding. Supervising agentic workflows can give you one level of insight, while prolonged manual struggle with the code gives you <em>mechanical intuition</em>.</p><h2>Putting a number on it</h2><p>To make sense of this we may be helped by a method. I suggest scoring each part of the system from 1 to 3 on two factors: cognitive complexity and severity (affecting business, money, or safety).</p><p>The CCD of a part of the system is roughly the product of the two. A complex domain with high severity amplifies the Cost of Cognitive Debt.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/ccd-matrix.svg" alt="Cognitive Debt turns expensive where complexity and severity meet." loading="lazy" decoding="async" /><figcaption>Cognitive Debt turns expensive where complexity and severity meet.</figcaption></figure><p>What the score decides is level of supervision. A high CCD means agents run supervised or not at all. A low one means we can let them run unsupervised, as long as we plan for the repayment.</p><p>The point of the score is not precision. It is to force the conversation to happen per domain, and to happen before the debt is taken on rather than after a meltdown.</p><h2>What must be understood</h2><p>Now, assuming that we have encapsulated our system into separate domains, or a layered architecture, we can address this problem with some granularity.</p><p>It seems natural that the core business rules of a system need to be well understood by not just developers, but by leadership and stakeholders as well, and this should result in a conservative view of what parts of the system we can expose to agentic workflows, and to the risk of Cognitive Debt.</p><p>For instance, we would be wise not to introduce unsupervised agentic workflows in layers of a system that are concerned with critical business rules. This part of the system would score a 9.</p><p>Similarly, it seems obvious that any infrastructure layer that could bring the system down for any period of time would have to be supervised and understood by humans. These parts of the system would score a high CCD value as well.</p><p>So far I agree with the agentic skeptic perspective.</p><h2>Loss we can accept</h2><p>But below these levels of system architecture, the impact of Cognitive Debt may become a little more contained, given a modular architecture. Inside of a non-critical domain, within a contained blast radius, we could allow ourselves to accumulate some Cognitive Debt, as long as the cost thereof would be outweighed by potential gains.</p><p>There is still a risk, but it is contained. We could still have a Cognitive Meltdown within this scoped context, but not all domains are equally essential for the system fulfilling its purpose, so we could make a risk assessment and accept this Cost of Cognitive Debt.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/ccd-by-layer.svg" alt="Modularity makes Cognitive Debt containable" loading="lazy" decoding="async" /><figcaption>Modularity makes Cognitive Debt containable</figcaption></figure><h2>Paying it back</h2><p>Now, in finance, debt is something that you are expected to pay back eventually. If we are to introduce the idea of calculated acceptance of Cognitive Debt, it makes sense to plan for how to get out of the debt. No one in their right mind would borrow more than they are theoretically able to pay back.</p><p>Repayment here means a human rebuilding the theory, reshaping it into something a person can hold in their head, and building mechanical intuition for how it behaves.</p><p>We can extend the metaphor by introducing interest. The cost of repayment is not constant, it grows. Every month the code drifts further from anybody's mental model, the people who held the surrounding context move on, and incorrect assumptions are hardened into surrounding code. Debt you meant to repay next quarter is more expensive next quarter than it is today.</p><figure class="md-figure"><img src="https://epiq.sh/blog/images/ccd-interest.svg" alt="The gap between what you set aside and what it now costs is the interest." loading="lazy" decoding="async" /><figcaption>The gap between what you set aside and what it now costs is the interest.</figcaption></figure><p>Agentic workflows are here to stay and we need a framework for reasoning in a methodical way about non-deterministic workflows in products that entire businesses rely heavily on. In the past leadership and stakeholders were often oblivious to the risks and impact of architectural decisions, relying heavily on responsible technical experts, who often saved the product by understanding the business needs. Now that many technical experts have resigned themselves to the same state of delightful ignorance, we need to address this risk in a structured way.</p><p>Is this a reasonable way of addressing and reasoning about the risk introduced by agentic workflows? Leave a comment below and share your thoughts.</p>]]></content:encoded>
		</item>
		<item>
			<title>Vision drift: workflow replay to the rescue</title>
			<link>https://epiq.sh/blog/vision-drift.html</link>
			<guid isPermaLink="false">https://ljtn.github.io/epiq/blog/vision-drift.html</guid>
			<pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate>
			<dc:creator>Jonatan Lampa</dc:creator>
			<category>ai</category>
			<category>agents</category>
			<category>productivity</category>
			<category>tui</category>
			<description>Harness engineering has recently popularized the idea of containing architectural drift in agentic workflows.</description>
			<content:encoded><![CDATA[<p><img src="https://epiq.sh/blog/images/vision-drift.webp" alt="The epiq board running in a terminal, four columns of issues on a dark ground" /></p>
<p>Harness engineering has recently popularized the idea of containing <strong><em>architectural drift</em></strong> in agentic workflows. What might be missing in the discussion is a similar issue on a higher level - <strong><em>vision drift</em></strong>.</p><p>By vision drift I mean that the implementation no longer drifts only from the architecture - it drifts from the original product intent. This could happen as a result of agents administrating their own work unsupervised for too long. And it seems like restricted project management tooling is a risk. As long as issue trackers only present a static snapshot of the workflow rather than a traceable, repeatable story, drift may go unnoticed.</p><p>While Git solves this for code, issue trackers essentially lacks this capability, and while excellent at answering the question “what's going on right now?”, they fail at answering the question “how did our work in this area evolve last month?” or “what went on this time last year?”, or “how did we get from there to there?”.</p><h2>Workflow audits</h2><p>When I set off to build <a class="inline-link" href="https://epiq.sh/" rel="noopener">Epiq</a>, this was not a concern on my radar. Agentic coding was something I had heard distant rumors of, and in fact I was just <a class="inline-link" href="https://epiq.sh/blog/ideal-developer-experience.html" rel="noopener">pursuing the ideal developer experience</a>. This pursuit did however result in an issue tracker with some uncommon properties. One of these is the ability to inspect historical state by time-traveling, and replay historical board states.</p><p>Initially I thought of it as a gimmick feature, imagining the wow-factor of replaying board layout the past 2 weeks as a little movie. I thought it would help out with retros and reflection of how much (or little) work had been accomplished. Not until I set out to run my first fully autonomous agent workflow did I notice how board time-travel was going to be an essential feature for anyone serious about staying in control.</p><h2>Questions raised</h2><p>It went like this: I had made a master plan together with Claude, and asked it to administrate the plan execution and breakdown using Epiq. As I returned two hours after it set off I was delighted to see that all of the issues had moved across the board to the review column. I spent an hour or two reviewing the code. It all looked great. All solid, tested and well functioning code, following existing architectural patterns in the codebase. Only, now I was incredibly interested in how the work had elapsed. I addition to what could be inferred from the code, I found myself asking:</p><ul><li>How did we get here?</li><li>Had any new tickets been created not anticipated initially?</li><li>Has the plan been revised?</li><li>Had any other issues on the board been blockers?</li></ul><p>A single agent working independently for two hours already left me wondering how the implementation evolved. I asked myself what happens when twenty agents collaborate over three days.</p><p>How was the work coordinated? Had they wastefully worked against each other, or in harmony, building on each other's work? Most importantly - had they abandoned, or revised the initial vision?</p><p>I could see how a snapshot state of the board no longer satisfied these questions, and unless you sat and monitored the board evolution in real time you would be unequipped to answer them.</p><h2>The full story</h2><p>Luckily, with Epiq, this was but a single command away: “:replay 2h”. That was all it took for me to see the whole session replayed as a time-lapsed movie. It dawned upon me that Epiq, with its persistence model, accidentally offered powerful functionality that will be required for humans to stay in charge of the vision.</p><h2>The future of project management tooling</h2><p>It seems like agentic coding isn’t going to go away any time soon. And given that we do some course corrections in our methodology after the initial hype I can see its given place even in serious productions, given that we reserve for humans the ability to stay in the loop - in charge.</p><p>With agents getting more and more autonomy, the risk of potential vision drift calls for better workflow auditing. Just one step up from architectural audits it seems like the natural next step in the discussion surrounding agentic workflows.</p><p>Git made the evolution of source code inspectable. I believe the next generation of project management tooling will need to make the evolution of intent inspectable.</p><p><em>In case you are interested 👉 <a class="inline-link" href="https://epiq.sh/" rel="noopener">Explore Epiq</a></em></p>]]></content:encoded>
		</item>
		<item>
			<title>In Pursuit of the Ideal Developer Experience</title>
			<link>https://epiq.sh/blog/ideal-developer-experience.html</link>
			<guid isPermaLink="false">https://ljtn.github.io/epiq/blog/ideal-developer-experience.html</guid>
			<pubDate>Fri, 03 Jul 2026 00:00:00 GMT</pubDate>
			<dc:creator>Jonatan Lampa</dc:creator>
			<category>git</category>
			<category>tui</category>
			<category>productivity</category>
			<category>devex</category>
			<description>It seemed like the ultimate developer experience - tracking issues in the terminal, editing them the editor of your choice.</description>
			<content:encoded><![CDATA[<p><img src="https://epiq.sh/blog/images/ideal-developer-experience.webp" alt="The epiq board running in a terminal, four columns of issues on a dark ground" /></p>
<p>It seemed like the ultimate developer experience - tracking issues in the terminal, editing them the editor of your choice. I contemplated the most appealing alternative workflow I could think of. This was it.</p><p><em>Epiq is a distributed issue tracker built on top of Git, and this is the story of how it came about.</em></p><h2>Two weeks, at most</h2><p>Fingers itched to get started when I pitched the idea to a colleague as we walked down the office corridor, paper coffee cup in hand. My colleague nodded politely, almost as if he too thought it would be cool. No more than two weeks, at most, was my bold prediction.</p><p>Now, before judging my poor ability to foresee complexity, the initial scope of the project was quite limited. A rendering engine for the terminal wouldn't be that hard to put together. At some point I would probably need to synchronize state, but surely it could just &quot;synchronize over Git&quot;.</p><p>Work began the same night. About two hours in I was struck by a moment of unusual clarity and abandoned the idea of creating my own rendering engine. Lo and behold, someone else had already invented the wheel. Enter Ink - a React wrapper that outputs ASCII instead of HTML. A brief moment of sanity had saved me six months, the time plan was within reach, and a few hours later there was a skeleton in place.</p><h2>Not the browser</h2><p>Now I just needed to hook it up to the keyboard, and ... This is where I realized that the terminal is not the browser. In the terminal there is no given way to select anything, let alone navigate via the keyboard, or move things around. “Things” weren’t even a thing. I’d need a node tree representation of the program, somewhat similar to the browser DOM, and a navigation engine to go with it.</p><p>Wrote up what seemed like a generic approach to navigation nodes. Tada! - all of a sudden you could select things and traverse the node tree via the keyboard. I was two weeks in, I could see something on the screen, and the original estimate was out the window.</p><h2>Calm seas</h2><p>It was time to get the commands in place. Epiq was to have a vim-flavor but with a modern user experience, featuring contextual auto-completions, hints and syntax highlighting. I approached the problem with caution, and wrote a proper engine for the command parsing, mapping and validation. Smooth sailing. Attention shifted to state management.</p><h2>Casually walking into Mordor</h2><p>The project was practically done at this point, and having implemented state management a thousand times before, this was going to be a walk on the beach. It was just a matter of writing the in-memory state to file, perhaps version it, and call it a day. Then it dawned on me that relying on Git for synchronization was going to create the mother of all merge conflicts - all the time.</p><p>Aha, thought I. I could just split up the state in fine granular, versioned pieces, one file per version. Then, came the next realization - ordered children. This meant arrays, and concurrent edits to the same array meant … merge conflicts.</p><p>To my delight at this point I learned of the esoteric technique of fractional indexing, after counseling sessions with an LLM. The idea behind fractional indexing is to assign certain ranks by halving in an absurdly vast number space (shout out to BigInts). Re-ordering was but a single node edit.</p><p>Then it dawned upon me that even now you could get concurrent edits to the same index. I set out to explore an array of alternative approaches. Even encoding ranks in file- and folder names, renaming files instead of co-editing the same file (it actually works!). Eventually I came across a solution that reliably avoided merge conflicts, but quickly noticed unreasonable amounts of claimed disk space. Extreme normalization meant thousands of tiny files, and as files claim a larger block of “growing space” than they initially need, even a fairly small board would claim megabytes of disk space.</p><h2>The big rewrite</h2><p><strong>This wasn't going to convince the nerds</strong>, and I imagined only nerds would actually care for this project. About seven months into the project it slowly dawned upon me that the persistence paradigm I was trying to shoehorn in was a bad fit.</p><p>For some time the idea of event sourced state had appeared on my radar as a viable path forward, but I could never muster the courage to think about it, as it meant a full rewrite. After trying almost every other trick in the book, I allowed myself to think about it a little more. If state could be represented as append-only entries to user-scoped event-logs, all merge fears could be buried forever. That is when I decided to throw away the entire persistence layer, months of work, and a solution that actually worked - betting everything on event-sourcing.</p><p>Six weeks later the event-sourced state engine fired up for the first time. To my amazement, it worked! Or, it seemed to work, at first. Then I was struck by the realization that multiple concurrent users were destined to break the model. Without a central broker Epiq was relying heavily on ULIDs, (time encoded ids) for harmonizing user logs into a shared authoritative log. A user with a clock drifting ever so slightly would risk messing up the order, fatally corrupting materialization, with ever accelerating drift.</p><h2>Clock Drift</h2><p>The solution to this problem arrived in the car (a 2009 Hyundai i20, for reference) during an 11h road trip to northern Sweden. I realized that entry ids had to be a composite - an id plus a reference to the last known “edge”. Only in the case of multiple entries pointing to the same edge would time resolve order, now contained and with non-fatal consequences as there would be a common understanding of the world.</p><p>Put it to the test as soon as the road trip was over, and - it worked. This, I later learned, is a very old idea in distributed systems: when clocks cannot be trusted, you stop reasoning about physical time and instead reason about causality. Leslie Lamport has written about this in his 1978 paper <em>Time, Clocks, and the Ordering of Events in a Distributed System</em>.</p><h2>Succeed or fail</h2><p>It was becoming increasingly evident that resilience was of utmost importance in a distributed system. TypeScript, however, was lacking in strictness as function signatures don’t express possible exceptions by default. This led me to introduce a custom result type - a union of success and failure states, forcing me to consider every error path at all times. This turned out to be a distinct leap in terms of the reliability and traceability of the system. Shout out to <em>Effect</em> - which inspired this design choice.</p><h2>Git doing its thing</h2><p>Nine months in, and the actual state sync would just be a minor detail before going live. Just had to write the event log to file on the checked-out branch and have Git do its thing. By now it may not come as a surprise that this turned out to be another rabbit hole, or shall we say, <em>another rabbit mining shaft</em>?</p><p>My initial approach of persisting the logs to the checked-out branch and merging occasionally, turned out to be the equivalent of trying to hold a conversation in the subway with a person on a different train, going the other direction. Rewrote the synchronization layer multiple times. Epiq needed a single source of truth, but how to achieve it remained a mystery. Many rewrites later it appeared that it just wasn't possible to achieve multi-user concurrent edits, using Git as a backend.</p><p>Then I discovered Git worktrees, a Git feature escaping my attention for the longest. To my amazement Epiq could have multiple branches checked out simultaneously and persist state in a shared, dedicated state branch while the user worked in another! One final rewrite later, at the end of the rope, I put it to the test, and… It worked - <em>beautifully</em>. Multiple clients making concurrent edits as if this was the ordinary state of the world. Over Git. In the terminal. No central broker.</p><p>My mind was blown. About a year after that conversation in the corridor, paper coffee cup in hand, what started off as an idea was now an actual thing that you could interact with.</p><h2>Ignore the invitation</h2><p>In hindsight, the initial vision and implicit constraints paved the way for an interesting architecture. It made Epiq vendor agnostic, terminal-first, offline-first, and gave it a traceable, replayable state (yup, there is a command for replaying your board history like a movie).</p><p>For every solved problem there is a pile of failed attempts, for every failed attempt there is an invitation to give up, or worse - compromise.</p><p>A compromise often gets you to the finish line twice as fast, but the destination is rarely half as interesting.</p><hr /><p>_If you're curious, Epiq can be seen at: <a class="inline-link" href="https://epiq.sh/_" rel="noopener">https://epiq.sh/_</a></p><hr /><p><em>Editor's note: Epiq finally launched on HN May 16, trending on the front page for more than 24h. Since then Epiq has been equipped with two of most requested features - a single binary and an optional browser GUI.</em></p>]]></content:encoded>
		</item>
	</channel>
</rss>
