Andrew Loutfi

Workbench: a development harness I built for myself

Andrew Loutfi · · 9 min read


The way engineers work has changed drastically in a year. In April, GitHub said it had revised its capacity target from 10 times current scale to 30 times. It pointed to a sharp rise in agentic development: more repositories, pull requests, API traffic, automation, and giant codebases. It is easier than ever to sling code. Even the place where we put the code is trying to catch up.

"What's the best way to write code with AI in a professional software setting?"

If anyone tells you the answer is a tool they wrote, you should tell them to go fuck themselves. It depends on how you work.

There are a lot of good tools out there. I just do not want to sink a bunch of time into something that changes underneath me, makes me relearn my own job, or gets weird because I use it in a way its creator never imagined. So, with that disclaimer stated, I built a tool.

I call it Workbench. It is a private Git repository that carries the rules, roles, scripts, and bits of context I use while working with coding agents. At the time of writing it has 17 agent roles, 12 reusable workflows, and 13 remote branches. The shared branch is only 30 commits old. The complete branch graph has more than 200 commits because most of the actual life happens away from the shared branch.

That makes it sound far more intentional than it was.

A car is a car if it gets you there

My first requirement was simple: it had to be intuitive for me to use and manage.

My mother always said a car is a car if it gets you from point A to point B. If it does not, it is a piece of junk. If I were building Workbench for anybody else, "intuitive for Andrew" would be a garbage requirement. I am not. I do not want to force people to use my shitty harness and I do not want to spend a lot of time maintaining my shitty harness.

But here I go writing a fucking blog post about it.

The interface is mostly stuff I already know: Git, a Makefile, a multi-root editor workspace, and a few directory conventions. The first commit landed on April 5, 2026. It already had specialist agent roles, a handful of workflows, and opinions about how changes should move through Git. Another commit added a make add target so I could register a repository and put it in the workspace without doing the bookkeeping by hand.

Some early ideas were junk. I added a hook that was supposed to stop an agent before it pushed to main. It fired on commands that had nothing to do with a push. I narrowed it. It still depended on a runtime interception mechanism I did not trust, so I deleted it three weeks later. The rule survived in the written contract. The clever enforcement did not.

That sequence is roughly how Workbench has been built: use it, hit something stupid, add the smallest rule that would have prevented it, then delete the rule if it creates a different kind of stupid.

The technology merry-go-round

My second requirement was that I did not want model provider lock-in.

Part of being an engineer is accepting the cyclic, exciting, and occasionally exhausting nature of the "technology merry-go-round," as one of my professors called it during my master's program. Some people hate this and move into management. Or they go farm potatoes.

Other engineers chase the new shiny. They sink a lot of time into the how and learn every corner of a tool before they are sure it deserves the attention. Sometimes new technology sticks. Sometimes it does not. Remember Warp? Sometimes it enshittifies.

Model clients are not exempt. Leaderboards flip. New models arrive with new prompting advice. Usage limits show up in the middle of a session. A provider has an outage and suddenly the workflow you carefully shaped around it is a very expensive loading spinner.

Workbench started out tied to one client because that was what I used. By June that arrangement was already irritating me. I moved the durable instructions into a neutral .ai/ directory and left client-native files around the edges as adapters. The rules about worktrees, documentation, security, and Git live once. One client can expose them as native agents, hooks, and slash commands. Another can install checkout-local skills and apply the same agent specs as role checklists.

The adapters are not identical because the clients are not identical. I am not trying to hide that. I want to keep the workflow when I swap the engine, not pretend every engine has the same bolts.

The same thinking produced provider handoff. If one client gets slow, burns through a usage limit, or falls over, I start the handoff from the client I am switching to. It looks up the old session's local artifacts, resolves the actual repository and branch, and writes an inspectable package with Git status, filtered diffs, safe untracked files, and a summary. Secret-shaped paths are skipped or redacted.

It is not seamless migration. It is a crash cart. A chat transcript can be wrong, stale, or unavailable, so the destination client has to verify the story against the repository.

Even the provider-neutral wiring has produced its own stupid failures. The first skill installer wrote checkout-specific links into a global directory. Whichever Workbench ran the installer last silently changed the skills another Workbench would load. That is exactly the kind of accidental lock-in and cross-contamination the repository was supposed to prevent. The default now keeps provider state inside each checkout. Global installation exists only as an explicit opt-in.

Git does the multiplexing

My third requirement was that the harness had to multiplex in two directions through Git.

The first direction is context. I want the same basic harness for professional work, a side gig, Skunk Works experiments, personal infrastructure, and wedding planning. The documentation or credentials appropriate to one of those contexts may be useless or dangerous in another.

Long-lived Workbench branches separate those worlds. Each branch can register a different set of repositories as submodules, pin them to exact commits, and carry its own local mission context. The shared branch is a small kernel. The context branches are where the weird personal tuning lives.

The second direction is concurrency. I often have multiple agents working on the same larger problem. Each implementation session gets its own Git worktree, its own derived branch, and a clear integration order. The target repositories remain independent. Draft pull requests are where the work comes back together.

I earned that rule with a race. Two agents worked from the same checkout and targeted the same feature branch. --force-with-lease did its job and prevented one from overwriting the other, but I still had to stop and repair the branch with a rebase. The instructions used to say agents could use worktrees. Now they say every session and delegated agent gets one by default.

Worktrees solve filesystem collisions. They do not solve coordination. The Git object database, refs, configuration, and remote still have shared parts, so somebody has to decide who owns which branch and which change lands first. That somebody is usually the orchestrating agent, which is a sentence I would have found exhausting a year ago.

GitHub is the front end between machines. I run a lot of implementation sessions on a machine in my homelab and SSH into it during the day. If I need to dig into a change myself, I push the branch and pull it onto my laptop. The agents do not need a shared chat history or filesystem. They need a branch, a commit, and enough written context to explain what the hell they were doing.

This does make GitHub a dependency. The availability report at the beginning of this post is not lost on me. If it goes down, remote coordination stops. Local Git still works, which is more graceful degradation than I would get from putting the entire workflow inside a hosted agent dashboard.

The unsexy half

The faster agents create things, the faster bullshit accumulates around the things.

Every worktree wants another dependency directory. Every context branch can drift from the shared harness. Instructions that were accurate three weeks ago can become actively wrong after the repository changes. A generated bootstrap that points a submodule back to Workbench contains an absolute path, so moving the checkout to another machine means regenerating it. Scratch files wander into child repositories. Credentials end up in places that are convenient right until an agent packages the directory for handoff.

Workbench now spends a suspicious amount of effort on deleting and checking things. It profiles worktree disk usage. Cleanup commands default to dry runs. Audits look for stale branches, old submodule pins, broken context links, provider configuration that escaped into global state, and generated slop that landed in the wrong repository. Scratch output has one ignored home at the Workbench level because child repositories should not inherit my mess.

The handoff tooling went through an adversarial review for the same reason. The first version was basically a snapshot script. Review found that it could copy secret-looking files, include sensitive diffs, follow unsafe filesystem entries, and accept shell input through a dangerous Make path. The current version filters, redacts, records what it skipped, and treats the old session as untrusted context.

Security and hygiene are where I will tolerate complexity. I can live with a plain Make target. I cannot live with a convenient recovery tool quietly copying credentials into an artifact.

Where it is now

Workbench is three months old and uneven in all the ways a personal tool is allowed to be uneven.

The shared branch has been stable since early June. Most current work lives on persistent context branches, and some of those branches have drifted. There is still no neutral way to define every external tool server once and generate both clients' configuration. One client exposes agent controls that another does not. The context cascade uses machine-specific paths. Worktrees share enough Git state that integration still needs discipline. Validation is local rather than enforced by hosted CI.

One adapter check in my current branch is red as I write this because a newer workflow is missing metadata that the validator expects. That is not a particularly triumphant detail for a post explaining my development harness. It is the current state, and the validator did exactly what I wanted by making the drift obvious.

I do not think Workbench is the best way to write code with AI. It is the best way I have found so far to keep my own habits legible while the tools rotate underneath them. Its rules are mostly scar tissue from something that annoyed me, broke, leaked, raced, or ate disk.

The car test still applies. If the harness gets me from intent to a reviewed change without becoming a second job, it is a car. If maintaining it starts taking more time than the work it is supposed to support, it is a piece of junk.

Writing this post has not helped its case.