The bottleneck in AI coding workflows has shifted from writing code to managing context

Priorities, precise instructions, task claiming, isolated workspaces, long-term memory, and human judgment determine whether an agent becomes a stable engineering system or remains a one-off assistant.

By From chaos to context: Building an AI dev workflow09-01 · 16 min read⚑ Research paper
🎧 Listen to the podcast0:00 / --:--
0:00 / --:--
Deep dive · XiaoHu Explains
One-minute overview
  • Each task goes through priority selection first, then loads entity history and rules, and only after claiming and environment isolation is it handed to the agent for execution.
  • Directives govern behavior boundaries, procedures handle repetitive flows, and tools manage deterministic data operations—mixing these layers creates new context waste.
  • Hybrid retrieval, proactive injection, and boot gates handle precise recall, automatic presence, and forced startup actions respectively; old memories still need source, age, and invalidation controls.
  • Humans spot errors in goals, architecture, and process, while AI makes rapid edits and verifications within a clearly defined direction; team governance is an extension of the author's personal practice.

Gregory Havenga, a GitLab blog author, worked on early versions of the AI security features “Explain this vulnerability” and “Resolve this vulnerability.” Back then, the AI was closer to a suggestion tool: a person made a request, the model provided an explanation or a proposed fix, and the person brought the result back into the codebase. With the arrival of agents, the boundaries of the work changed. An agent could now read the repository, modify files, and run tests — it began executing tasks directly.

As execution capabilities grew, Havenga’s problems started to look more like traditional software system failures. The same commit message rule might need to be corrected three times in one session. Three parallel sessions could claim the same task, modify the same files, make contradictory decisions, or even commit or delete work that another session hadn’t finished. The model wasn’t writing less code; it just lacked persistent state, resource ownership, and cross-session memory.

He gradually rebuilt his personal workflow into a context system. The system first decides what should be done, then loads history and rules for the task. Before acting, an agent claims the work and enters an isolated environment. Repetitive processes get codified into procedures, and stable data movement is pushed down into tools. A person reviews the direction, architecture, and final correctness. Confirmed decisions, blockers, and processes are then written back into long-term memory.

This system explains a common paradox: model capabilities are improving, yet daily use is still full of repeated explanations and rework. Throughput is no longer limited just by the quality of a single generation. It also depends on whether context arrives at the right time, whether multiple executors can share state, and whether a human still holds system-level judgment.

How a task moves through the complete context system

Havenga’s work entry point has shifted from “open the editor and think about what to do today” to an automatically assembled priority queue. A new session first reads other still-active sessions, unresolved blockers, long-term instructions, and recent decisions. Then it pulls to-dos, active merge requests, and tracked epics from GitLab.

The queue order is also explicitly encoded. Stale items that haven’t progressed in a while and might fall out of view appear first. Next come reviews requested by others, because delays continue to block colleagues. Then come questions waiting for his reply and his own blocked merge requests. Everything else comes last. This step turns team blockers, task aging, and personal progress into a stable selection rule, which goes far beyond a typical to-do list.

Once a task is selected, the system starts assembling context for it. A merge request task goes through the following process:

  1. Read the merge request, related issues or epics for past decisions, unresolved blockers, and next steps left from last time.
  2. Retrieve rules and procedures that match the task type, such as comment guidelines, database migration processes, or review requirements.
  3. Check whether the work item has already been claimed by another session; if not, establish a claim and make the current owner explicit.
  4. Assign the session a dedicated git worktree and a separate test database, so file state and runtime side effects are isolated from each other.
  5. The agent modifies code, runs tests, and organizes results; a person reviews the goal, architecture, correctness, and whether merge criteria are met.
  6. When work is done, paused, or hits a new blocker, update status, release resources, and write new decisions, facts, and procedures back to memory.
01 SelectPrioritizeStale items, colleague reviews, pending replies, and blocked merge requests
02 LoadBring in contextHistorical decisions, blockers, standing instructions, and relevant processes
03 CoordinateClaim and isolateClaim a work item, spin up a separate worktree, and use a dedicated test database
04 ExecuteCode and testThe agent edits code, runs tests, and gathers evidence
05 Control pointHuman review of direction and correctnessArchitecture, boundaries, quality, and long-term maintenance costs
06 Write backSave decisions and blockersThe next session starts from the updated state
Reviewed decisions feed back into the next round of context
The agent works in the middle of the loop; selection, isolation, review, and write-back determine whether execution is reusable.

This sequence turns “give the model more context” into a checkable state transition. Context isn’t all dumped in at the start of a session. The system picks the task first, then loads information around the selected entity. An agent can’t just read the instructions and start working; it must first claim the work and isolate itself. Review happens before experience is written back, which prevents an unverified output from becoming a rule that every future session inherits.

The author says he can now use a 15- or 30-minute gap to push a complex task forward: check what’s at the top of the queue, have the system restore relevant context, then delegate coding and testing. This timing comes from personal experience and isn’t a universal productivity metric. The change it reveals is specific: where short blocks of time used to be spent recalling “where was I, why was I doing this, who’s still waiting on me,” that reconstruction work now falls to the system, letting a person move faster into judgment and delegation.

This way of working depends on active delegation. A person keeps the top-level goals across tasks and decides whether current work still serves the right direction. Agents accept clearly bounded tasks and maintain execution speed. The system keeps task state, history, and constraints out of a person’s short-term memory while preserving human directional control.

How a one-time correction becomes long-term control

Every new session risks starting from near-blank. Without persistent instructions, team conventions, codebase habits, and personal preferences get explained over and over. The problem isn’t just that “the model forgets.” Many reminders meant for humans aren’t executable in the first place.

“Be careful with comments,” “watch out for safety,” “write more professionally” — these have no clear trigger conditions and no action if something fails. An agent can verbally agree while having no idea what to check or whether it can continue when a check fails. The rules in the source text are much more specific: before posting a comment on behalf of a user, you must verify that the user ID exists; if you can’t confirm it, stop and ask.

Trigger: preparing to post a comment as a user
Required action: verify that the user ID actually exists
Stop condition: if it cannot be confirmed, stop, do not guess, and ask

This kind of rule governs both what to do and when to stop. Words like ALWAYS and STOP in all caps seem to improve compliance in the author’s usage, though the source doesn’t provide a controlled comparison. The practice that transfers reliably: rewrite abstract preferences into task triggers, mandatory actions, stop conditions, and checkable outcomes.

Instructions also shouldn’t be written out of imagination at the start of a project. Havenga’s approach comes from post-incident review: after an agent makes a mistake, first fix the current task, then ask which instruction might have prevented the same class of error. The person and the model together refine the wording, confirm it doesn’t break other scenarios, and only then save it. Persistent rules therefore correspond to a real failure and carry an explicit scope.

His agents-config once contained over a dozen specialized config files covering code review, merge request workflows, database operations, comment writing, and status updates. The current public config shows even finer-grained triggers: recall the review procedure before starting a review, recall database standards before a migration, load entity history before touching an MR, issue, or epic. Rules are triggered by task type, and each session loads only the standards it currently needs.

As rules grow, static files run into two problems. First, startup cost keeps rising — standards irrelevant to the current task still occupy context. Second, freshness becomes hard to manage; old processes can override new decisions. The author later moved many procedures and details into searchable memory, leaving only a few mandatory gates in the startup context. Stable, short constraints that apply to every task stay at the startup layer. Knowledge tied to a specific task type or entity is loaded on demand.

This forms a maintainable rule evolution path: failure produces a correction, correction becomes a local directive, high-frequency operations sediment into procedures, and procedures are then loaded via task triggers and memory retrieval. Rules are no longer just prompt attachments; they become the control plane of the workflow.

Parallel agents need work claiming and environment isolation

Opening three terminals and running three agents just adds three executors; it doesn’t automatically produce a parallel system. The failures listed in the source include: sessions modifying the same file; two sessions selecting the same work item; contradictory decisions made in isolated information; one session deleting or committing another session’s work; and multiple sessions investigating an already-solved problem.

These failures occur at different layers, and no single worktree can solve all of them:

Conflict Mechanism Limit
Files and Git Isolated worktree for branch, directory, and uncommitted changes The same MR can still be claimed twice
Test runtime Separate test database per session No task ownership or decision history
Work ownership Claim / release on an MR, issue, or epic No file or database isolation
Decisions and blockers Share stage, prior decisions, blockers, and next step No exclusive claim or resource isolation

A safe parallel task requires combining these mechanisms across the lifecycle. A session first checks claims, then reads entity history. After a successful claim, it creates or enters the corresponding worktree and points its test connection at its own database. During execution, it continuously records stage and blockers. On completion, it saves results, releases the claim, and cleans up unused workspaces. Unexpected exits also need expiration, takeover, or manual release policies, or the protection mechanisms themselves leave permanent locks.

The author’s public test infrastructure makes this isolation concrete: different sessions get different worktrees and test databases named after the session. They share the underlying Postgres and Redis services but not test writes. Scripts handle session creation, test runs, configuration diagnostics, and session deletion. The value isn’t just that tests can run in parallel; it’s that environments can be enumerated, inspected, and reclaimed.

The author first used file-based work notes to share state. Files persist across sessions, but they have three clear limits: they’re hard to search with different phrasings, they can’t be reliably linked to a specific merge request or function, and they don’t automatically resurface in the tasks that need them days later. A parallel system needs a shared service that can save state around entities, retrieve history, and proactively remind — extending notes files can’t provide these.

That need eventually led to opencode-memory. It serves both long-term memory and session coordination: multiple agents connect to the same backing service, so work-item claims, entity history, and related procedures are visible across sessions. File isolation stops “don’t edit the same place”; shared memory stops “don’t each redo work without knowing what the other decided.”

How memory moves from passive search to proactive context

The hard part of long-term memory isn’t saving chat history. It’s whether, at the next task, the system can retrieve a small set of relevant, still-valid pieces. Loading everything saturates the context window fast. Relying on a person to actively search turns “remember to check memory” into another manual burden.

What gets stored in memory

The author’s system stores decisions, blockers, procedures, facts, and session results, and links them to MRs, issues, epics, functions, and other code entities. After a piece of work ends, the system needs to leave behind not just “done” but also why this approach was chosen, which conditions remain unmet, where the next session should pick up, and what kind of error should trigger a stop in the future.

At the time of the source publication, the author reported the graph held more than 760,000 code entities, 15,000 memories, and 27,000 relationships. That’s a snapshot of a personal system at publication time. The public repository can verify its functionality and architecture but can’t independently reproduce the database scale at that moment.

How exact entities and fuzzy experience get found together

opencode-memory uses hybrid retrieval. Information with stable forms — merge request numbers, function names, error strings — fits full-text search well. Questions with variable phrasing, like “why wasn’t this approach chosen last time,” need semantic similarity as a supplement. Keyword search keeps exact entities from being drowned out by vector approximations; vector search lets different phrasings still hit the same decision.

The current public implementation puts text and structured state in SQLite with FTS5, and the vector index in LanceDB. New memories are persisted immediately; embeddings are computed asynchronously by a background task. This split shortens the write path and lets memories without completed vectorization still surface via exact search. The public implementation also shows memory age and links GitLab entities, helping a session judge whether a result might be stale.

From explicit recall to pre-call injection

The first version of vector search was useful enough for the author to start using it the same day, but it still required the agent to explicitly call recall. If the session doesn’t realize “there might be history here,” the memory store might as well not exist. The author later added proactive context injection: before each model interaction, the system automatically retrieves candidate memories based on the current request and entity, then puts the relevant results into context.

Mention a merge request and related decisions and blockers appear automatically. Prepare to write a comment and the comment standards show up. Start a repeated type of work and the relevant procedure gets recalled. Proactive injection turns memory from a tool that waits to be called into part of the request handling pipeline.

The author reports that in internal records over 30 days, proactively presented relevant context was effective about 91% of the time. Sessions with proactive injection averaged 0 explicit recalls; sessions without it averaged 17. The source doesn’t provide sample size, effectiveness labeling rules, or other control variables, so these numbers describe only his personal system. They still measure what was intended: proactive injection reduces the number of times a session must discover and invoke a memory tool. It doesn’t mean code quality improved by 91%.

The remaining failures led to the boot gate. The system keeps an extremely short mandatory trigger in the startup context, requiring the agent to pause and load key directives before executing. Proactive retrieval looks for content that might be relevant; the boot gate guarantees startup actions that must happen. Separately, they handle relevance and enforcement.

Proactive injection also brings new governance requirements. An old decision may be invalid. Semantic similarity could just be lexical closeness. Cross-project memories raise permission boundaries. So each memory needs a source, a timestamp, an entity scope, and an invalidation status; recall results need quantity limits and relevance thresholds. The quality of a memory system depends on rejecting wrong context, not just on how much is stored.

Slow changingStartup instructions
Saved
A few high-priority rules, stop conditions, permission boundaries
Read
Loaded at session start and kept resident
Risk
Too many rules crowd out task context
Fast changingActive working state
Saved
Claim owner, current stage, blockers, pending replies, resource usage
Read
Before task selection and before execution
Risk
Stale claims turn a guardrail into a bottleneck
Long-term accumulatedSearchable memory and SDLC graph
Saved
Historical decisions, procedures, code entity relationships, cross-project dependencies
Read
Retrieved by entity, keyword, and semantic relevance
Risk
Old decisions and bad retrievals must carry source, time, and invalidation status

Orbit supplements the cross-SDLC analytics graph; real-time task ownership stays in the working state.

Context isn't a single big file. The faster it changes, the more it needs separate state and explicit invalidation.

Local memory and GitLab Orbit also have different scopes. The local layer saves session-level decisions, blockers, procedures, and low-latency code recall. Orbit organizes projects, users, merge requests, pipelines, work items, security findings, and source code into a cross-SDLC property graph, answering questions like what a service change affects or which merge requests modified a certain file. GitLab currently labels Orbit as Beta and notes it provides analytical snapshots from the last indexing cycle, not real-time transactions. Task claims still must be handled by the shared state system.

What procedures, tools, and “check before build” each solve

Instructions, procedures, and tools often get lumped together, but this author's workflow keeps them in three separate layers. Instructions define behavioral boundaries—for example, verifying a user's identity before publishing a comment. Procedures lay out the full sequence of steps for a recurring task. Tools handle stable, mechanical operations like data reads, pagination, field selection, and format conversion.

When tracking a high-priority epic, the author no longer starts each weekly report from a blank prompt. The procedure first pulls the current state from GitLab, reads last week's snapshot, calculates what changed over the intervening period, and then produces a draft update with progress indicators. The data source, comparison baseline, and output structure are fixed, so any session can run the exact same flow. A human still decides whether anomalous changes need explanation and whether the final wording is accurate.

Once the data-gathering part of a process becomes stable enough, the author pushes it down into APIs, the OpenCode GitLab plugin, and a local MCP service. He has also contributed group uploads APIs and GraphQL mutations for MR workflows back to GitLab for his own AI development needs. The model directly calls tools that return structured results, which eliminates the need to rediscover endpoints, handle pagination, or guess at field relationships each time. That frees up the context window for comparison, explanation, and decision-making.

The three layers can be distinguished by how they fail:

Layer What it's best for What happens if misplaced
Directive Permissions, stop conditions, and non-negotiable working rules Overloading startup context makes it bloated and contradictory
Procedure Recurring flows with stable stages that still involve judgment Ad hoc prompts each time end up missing steps and inconsistent comparison baselines
Tool Data operations that can be executed and verified mechanically Letting the model handle it repeatedly burns tokens and introduces formatting or pagination errors

Cheaper tools have also had a side effect: going from idea to working prototype can take as little as a week, but the cost of reinventing the wheel has fallen just as fast. While searching for AI programming memory systems, the author found a large number of similar projects, and even a PyPI package with the same name that uses a different vector database. Early on, his own system didn't offer much more than existing projects. It took months of iteration and deep GitLab integration before maintaining a separate system had a clear rationale.

That's why the first step before starting any project should be checking what already exists. When an existing project covers most of the requirements, contributing the missing piece is usually more effective than building a near-duplicate from scratch. Havenga chose to upstream his changes to the OpenCode GitLab plugin because they could benefit more users. AI may accelerate implementation, but it doesn't remove the need to think through dependency evaluation, maintenance cost, and ecosystem collaboration.

The author describes this sustained investment as optimizing the tools themselves. Better APIs and MCP tools improve daily efficiency, which frees up time to keep refining the workflow—a compounding loop. At the end of his post, he notes that the memory system had already racked up 143 commits in under a month. That number doesn't prove quality, but it does show how quickly both the tool and the workflow are still evolving. Any static notion of an optimal configuration will soon be obsolete.

AI can fix code, but humans need to spot systemic errors

When the procedure is already in context, an agent can reliably execute many repetitive steps. Given a clear direction from a human, it can also quickly modify implementations and add tests. The difficulty arises when the program itself needs to be questioned.

A model won't typically notice on its own that a flow feels awkward, or realize that this is the third time a similar problem has happened in a week. It can complete the task at hand without recognizing that multiple local failures point to a single systemic defect. Someone who has worked on a production system for a long time tends to spot patterns in friction, repetition, and unusual combinations—signals that are rarely spelled out in a single task description.

Havenga once watched AI introduce a concurrency bug into his own tool. The local code worked, but the synchronous operation blocked the entire tool. The agent didn't recognize that its concurrency model had an architectural flaw, and kept implementing along the given task. Once a human pointed out that a proper async pattern was needed, the agent fixed it within minutes.

That case splits human-machine collaboration into two distinct levels:

  • Agents excel at executing changes within a given goal and constraints, running tests, and iterating quickly.
  • Humans are responsible for deciding whether the current goal is right, whether the architecture makes sense, and whether local success is creating systemic problems.
  • An agent can refine its own instructions based on explicit feedback, but a human still has to identify which repeated failures should be elevated to new rules.
  • Human review before merging isn't just about whether tests pass—it also checks correctness, design quality, and long-term stability.

The author captures this shift in value with the phrase "code is becoming a commodity." As the marginal cost of generating code drops, engineering value increasingly concentrates on deciding which code should exist, recognizing when an approach simply won't work, spotting inefficient patterns early, and providing direction to raw execution capability. The model amplifies both good and bad decisions—the faster it executes, the faster a wrong direction accumulates.

That also explains why automated review can't fully replace human assurance. A model can catch a wide range of local problems, yet still accept an implementation that shouldn't exist given the goal or architecture. Human control points need to move earlier, into task selection and design, while still staying in place before the final merge.

Tools go stale quickly; judgment and feedback loops stick around

The author has changed how he works several times over the past few months. He was still writing a guide on how GitLab's CREDIT values apply to AI when GitLab Act 2 replaced CREDIT with new principles aimed at the agent era—rendering the not-yet-published guide obsolete. It's a clear example of why trying to codify a permanent process around a specific tool isn't realistic.

What remains relatively stable amid change is the feedback loop: observe repeated friction, turn failures into directives or procedures, push deterministic data operations into tools, verify with real work that they reduce context rebuilding and conflicts, and adjust the control surface when side effects appear. The author's memory system is built on MCP, so it can serve OpenCode, Claude CLI, Cursor, and other agents that support the protocol. Clients will change, but persistent state, task claims, and searchable procedures can stay in a shared layer.

Some GitLab colleagues have already adopted variations of this workflow and report that the memory plugin improves their session experience. That feedback isn't a team-level productivity experiment, but it does show that people beyond the author can use the mechanism. Moving it to a team would require making the implicit assumptions explicit: who is allowed to write to shared memory, which decisions need approval, how claims expire, how sensitive context is isolated, who reviews old procedures, and what metrics would indicate that rework and conflicts are actually decreasing. These are team governance questions that follow from the author's practice, not results that are already validated in the original post.

The nature of the problem changes too. Early on, the question is "how do I get AI to perform a certain action?" Once a context system is in place, it becomes "how do I make sure it has enough of the right information before it acts?" And when priorities, history, rules, and tools all arrive automatically, the human can end up becoming the bottleneck for review and decisions. Teams then have to ask which judgments can be turned into standards earlier and which decisions must remain a human responsibility.

Havenga's workflow doesn't offer a final template to copy verbatim. What it provides is an engineering approach that grew out of real failures: use state management to parallelize work, use memory to preserve decisions across sessions, use procedures to standardize recurring processes, use tools to lower mechanical context costs, and keep a human looking at where the system itself is still wrong. Agent capabilities will keep changing, but the loop from failure, to constraint, to execution, to feedback remains sound.

Source
From chaos to context: Building an AI dev workflowGregory Havenga·2026-08-19·View primary source
Site note
Based on a personal workflow review by GitLab blogger Gregory Havenga; system scale and 30-day effect figures are self-reported by the author and were not independently reproduced.
↑ End of article
✓ This article is free to read in full

Members can copy or download the full Markdown, and let their AI subscribe and ask.

Read the original ↗
📮 New explainers, straight to your inbox

Found this useful? Leave your email — every new explainer, the moment it's out. Unsubscribe anytime.