AI-assisted software development in 2026. Part 1 of 2.
Designing AI-assisted software development workflows and understanding the harness primitives
This is one of those articles that I suspect will feel outdated pretty soon.
It is based on a talk I gave a couple of weeks ago in our small AI Engineering community in Madrid, madAI.es. Some people shared encouraging feedback and asked me whether I would repeat the talk or prepare a workshop on the topic.
Instead, I decided to write this article since I have other projects on my plate right now and need the time and attention to take care of them properly.
Because the original version became too long, I decided to split it into two parts. This first part focuses on designing AI-assisted, harness-based workflows for software development. The second one will focus on team adoption and the challenges that come with it. It will be published in the next few days.
I know this first part is still long, but I am not a big fan of breaking every topic into a series of short posts. In any case, I hope it brings you some value.
The expectations
When people talk about software development with AI in 2026, there is often an expectation that autonomous agents will be able to pick up work from a Kanban board, refine requirements, write code, review changes, deploy to production, and even trigger an SRE agent when an incident occurs, all without human intervention.
In the most ambitious version of this vision, agents also participate earlier in the process. For example, taking notes during discovery interviews, extracting requirements, and creating the first version of the tasks that will later flow through the autonomous delivery system.
Although we may eventually get there, it is not that simple today.
We need to ensure proper governance for these agents, remain audit-ready, and make sure rollback mechanisms still work in a context of increasing complexity.
That complexity comes from higher delivery throughput, more features moving in parallel, and more potential interdependencies between them. And even if we can deal with all of that, we also need to avoid going broke because of the AI bills.
The current maturity levels
I see three broad maturity levels in how companies are adopting AI-assisted software development today.
This is an intentionally simple and very opinionated model, but I think it helps illustrate the different levels of AI adoption that currently coexist across teams and organizations.
At the first level, we see people working individually with AI tools such as GitHub Copilot or Claude Code. They create their own prompts, adopt the skills and practices they find effective, and gradually build a personal setup around their workflow. However, this setup usually remains individual, so it is not standardized, documented, or shared across teams.
At the second level, the teams share common workflows built around harness primitives. Humans still supervise the work, make the key decisions, and drive the overall product development process. All teams contribute to a shared context flow, and domain and tech knowledge is captured, shared, and centralized through skills and other context artifacts.
At the third level, software is built by agents running in orchestrated loops. There is a much higher level of automation, especially around review, validation, observability, and incident response. Humans intervene mainly in cases of escalation or certain situations.
In many organizations, these levels coexist. For example, one team may still be experimenting with individual prompting, while another is already building structured workflows or exploring supervised autonomy.
This article focuses on the second level. Building solid practices at this stage will make it easier to jump into more advanced automation in the future.
A practical example
Before jumping into the concepts, I first want to illustrate how we normally work with an AI-assisted workflow through a practical example. In the talk, I did a live demo to build a new feature for a personal open-source project, a small web app to track my habits.
Note: this demo was, of course, a heavily simplified version of a harness-based AI workflow similar to the one we use in real engineering teams. Still, the important thing here is the underlying idea. In the following sections, I will summarize each primitive I used in more detail.
This little app allows me to log habits and track metrics to understand how consistent I am. This also includes bad habits, which can have an associated cost that I can visualize as motivation to reduce them.
Gathering the requirements
So, let’s say we want to develop a new feature: a streak tracker to ensure consistency with my goals. To start defining it, I invoke a skill, in this case /demo-requirements-interview, that interviews me to make sure we have a clear definition of the problem and the solution we want to build.
The requirements file is created in a git ignored directory. Alternatively, we could use a Confluence or Notion MCP to store it there instead of using a Markdown file, making it easier for the team to review and collaborate on it.
Once the requirements have been reviewed and we/clear the context window, we are ready to create an implementation plan.
Creating the implementation plan
Now it is time to create the implementation plan and the different user stories. To do that, we will use the /demo-create-implementation-plan skill.
This skill creates the plan and user stories following our best practices. For example, it ensures that every user story is vertical, meaning that it includes both backend and frontend functionality and can deliver something valuable and usable to customers.
Of course, we need to review and refine the user stories before implementing them.
Implementing the user stories
Now that we have reviewed the user stories, we want to start implementing them. However, because we used Opus 4.8 for requirements gathering and planning, we do not need such a powerful model for implementation. For this stage, we can switch to a cheaper model, such as Sonnet, to implement the features.
Once we have selected the right model, we will use the /demo-implement-task skill for this. It includes the team guidelines needed to ensure we follow practices such as TDD and other agreed engineering standards.
After implementing the first user story, running the tests, and reviewing the results, we can /clear the context window and continue with the next one.
A summary of the main primitives
In the previous example, we mentioned several primitives. Now let’s go a bit deeper into each of them.
The Agents Memory files: AGENTS.md (or CLAUDE.md)
AGENTS.md is the closest thing we have to a README for coding agents. It describes how an agent should work inside a repository.
But not everything should be in this file. It is tempting to put every instruction and guidance there. But since this file is always loaded into the context window, every unnecessary line will add noise and cost.
A good AGENTS.md should contain only the instructions that are broadly useful across the repository. More specific guidance should live elsewhere, like in skills, scoped rules, local documentation, or repository-specific workflow files.
Scoped Rules
Rules are scoped working conventions or instructions for a project, directory, filename pattern, or file extension. If the coding agent supports rules, they are loaded only when it works with files that match the defined paths or patterns.
---
paths:
- "backend/src/**/*.ts"
---
Backend conventions beyond the slice rules in `CLAUDE.md` and ...
## Value objects and dates
- Cross boundaries (HTTP ↔ domain ↔ DB) using the branded value obje...
- All dates on the wire and in DB columns are `YYYY-MM-DD`. Don't...
## Cross-slice composition
- A slice never imports another slice's `domain/` or `infrastructure/`....Rules influence the agent’s behavior, but rules alone do not enforce correctness. For real enforcement, they should be combined with tests, linters, CI checks, or hooks.
Skills
Skills provide the agent with procedural instructions for performing a specific, repeatable task. They are usually structured as folders containing a SKILL.md file with metadata and instructions, and they can also include supporting resources such as reference files, templates, or executable scripts.
They do not need to pollute the always-loaded context, because in a regular work session only their descriptions are loaded upfront. The full skill content is loaded on demand, when the skill is invoked manually or when the coding agent decides it is relevant to the current task.
---
name: demo-requisites-interview
description: Capture requirements for a new feature before
implementation planning. Reads ./docs/PRODUCT.md, ./docs/ARCHITECTURE.md,
and ./docs/UBIQUITOUS_LANGUAGE.md,
surfaces conflicts with existing product decisions and domain terminology,
asks closed clarifying questions,
and writes .workflow/requisites/<id>-<english-feature-slug>.md.
Use BEFORE planning or implementation, not for in-flight tasks.
argument-hint: "<feature request, feature name, slug>"
arguments:
- feature_input
disable-model-invocation: true
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Demo requisites interview
Capture requirements for `$ARGUMENTS` under `.workflow/requisites/` before
any planning.
Optimize for a sharp problem statement, not a comprehensive design doc.
## Process (in order)
1. Read `./docs/PRODUCT.md` and `./docs/ARCHITECTURE.md`. If either is...Also, they can be “user invocable,” meaning that they can be available as a slash command. This makes skills useful for activities such as PRD creation, implementation planning, code review, security checks, documentation checks, migration guides, or release-readiness reviews. They help teams codify repeatable practices without putting every possible instruction into AGENTS.md or CLAUDE.md.
Skills can also be distributed through plugins, making them shareable across teams and projects. This is important because it turns local knowledge into a shared artifact.
Plugins and marketplaces
We can distribute skills, rules, and other primitives as plugins that can be installed in our IDE and made available to our coding agents. These plugins can be distributed through marketplaces like https://skillsmp.com
You can also build a private marketplace for internal plugins. After all, they are just code repositories.
The context window
The Context Window is a concept we should always keep in mind. When we work with a model, we operate within a context window. That context window is limited to a certain number of tokens, and it gets progressively filled during our working session.
When we start a new session or clear the context window, some elements may already be loaded before we write the first prompt. In tools like Claude Code, this can include the system prompt, project memory files such as CLAUDE.md, auto memory, MCP tool names, skill descriptions, and other configuration elements.
The context window is the model’s working memory for the current session. As the agent reads files, calls tools, receives outputs, and generates responses, that memory fills up. When the session approaches the limit, some tools can compact the conversation by replacing part of the previous history with a structured summary.
Compaction is useful because it allows the session to continue, but some details, decisions, instructions, or nuances may be lost. For that reason, we should avoid relying on uncontrolled automatic compaction as part of our normal workflow if possible.
Some models have a large Context Window. A larger context window helps because the model can fit more information before compaction is needed. But bigger doesn’t mean better. More context also means more noise, more potential conflicts, and more chances for the model to focus on the wrong information.
That is why I will insist throughout this article on a simple practice: work in small, atomic tasks in every iteration, and clear the session before moving to the next one.
If we think about it, this is not a new idea. Extreme Programming and other iterative methodologies have defended small tasks for years because they reduce risk, improve feedback loops, and make work easier to review. With AI-assisted development, this principle becomes even more important.
Tools and MCPs
Tools enable coding agents to execute operations and interact with the “outside world”. A tool is usually exposed to the model as a function, but behind the scenes it may be implemented as an endpoint, a command, or a script.
MCP servers connect agents with external systems by exposing tools. For example, we can connect Claude Code to systems such as Jira, Datadog, or PostgreSQL through MCP servers.
In 2025, MCP became heavily hyped, so today we can find MCP servers for almost everything. But this requires us to be cautious. Before adding an MCP server to our workflow, we need to understand if we can trust it.
Not all MCP servers are well-designed. Some expose too many tools, return verbose responses, or make it hard to control the amount of context sent back to the model. In those cases, a CLI wrapper can sometimes be more efficient because it gives us more control over filtering, formatting, and limiting the output.
An interesting MCP server (it is also available as a CLI) I really recommend for software development is Context7. It provides recent and accurate documentation for coding libraries and programming languages, helping fill the gap left by model training cutoffs and ensuring the model has access to up-to-date documentation and syntax when coding.
Subagents
Instead of asking the main agent to handle everything in the same conversation, we can delegate specific work to subagents.
The benefit is that these agents can work with their own focused context and return only the relevant findings. This prevents the context window from being flooded with intermediate reasoning, noisy details, or unrelated information.
A typical use case for a subagent is acting as a reviewer. By using a different model with a clean context, we can avoid reviews being biased by the main conversation’s context window.
Hooks
Hooks can provide guardrails around the AI lifecycle. They are especially useful when we need deterministic checks. They allow teams to run predefined checks before or after specific agent actions.
They are useful for enforcing formatting, running tests, applying linting rules, collecting metrics, validating outputs, checking permissions, and adding safety gates before the workflow moves forward.
Tokenomics
Each of the previous primitives has implications for context-window usage, token consumption, and, of course, cost. And the cost can become a challenge we need to address.
Understanding the cache
During a coding session, the agent will usually rely on caching. This matters because cached tokens can be much cheaper than non-cached tokens, sometimes by an order of magnitude depending on the model, provider, and pricing model.
When we build custom agents directly on top of model APIs, we usually have more control over what gets cached and when. With coding agents such as Claude Code, Cursor, or similar tools, we don’t have control, or it is very limited. However, it is interesting to understand what tends to invalidate the cache.
In that sense, this awesome article by Abhishek Ray and its video helped me a lot to understand the Claude Code cache. For other coding agents, concepts may be similar.
In summary, during a session with the agent, since it is stateless, it needs to have all the conversation context in every iteration.
Caching is prefix-based, so when the beginning of a request remains unchanged, the agent can reuse previously processed tokens and only process the new content appended at the end.
So if we change anything in the beginning, like the memory, tools, or skills description, during the session, the cache will be invalidated. It will be invalidated in any case, at a certain time, but knowing this, we can be conscious of preventing premature invalidation.
Some other things to consider
Small tasks, small context window.
As I said before, this principle is not new. We already know that small tasks are easier to review and allow teams to get feedback faster. When working with agents, this principle becomes even more important because it reduces the amount of context needed to focus the model on a concrete piece of work.
With well-designed context, small tasks reduce the risk of hallucinations and make it easier to keep the context window clean between iterations because once the task is finished, the context can be cleaned or reset before moving to the next iteration.
I warned you I was going to repeat this idea.
Clear the context over compacting.
A large context window does not remove the need for good context management. Even if the model supports a large context window, if we let the agent load too much context, or even worse, rely on compaction, it will probably lose relevant information along the way.
Again, the safest approach is to work in small tasks and clear the context window between iterations.
Select the Model wisely.
Avoid using premium models for tasks that can be handled by less expensive ones. For example, we can use a model such as Opus 4.8 to create implementation plans or discuss architectural decisions, while using Sonnet to implement individual tasks.
Avoid large memory (CLAUDE.md / AGENTS.md)
Memory files are loaded automatically, so we need to keep only the minimum necessary context in them. Since they are scanned recursively across the repository, we should always take this into account.
If we have context that applies only to specific folders, it is usually better to use rules instead of memory files.
If you have a scheduled token window, organize your work strategically around it
If you have time windows for your tokens, ensure your non-productive work aligns with your productive hours.
Make sure that preparation work, such as requirements definition, planning, reviewing documents, or writing prompts, happens before your productive coding window starts.
Distribute the tasks by role (example: PM leading the PRD generation)
In software engineering, only a small part of the work is about writing or reviewing code. We also need to ensure that the problem is well defined, the requirements are clear, and the implementation plan is solid.
These tasks will become more important than ever when working with AI-assisted workflows, and collaboration around them is essential. Teams can discuss and iterate on the documents generated during these phases, such as PRDs, requirements documents, or implementation plans.
If we distribute ownership of these artifacts across the development process, people can use their time, attention, and token quotas on the work they are actually accountable for.
Evaluate CLI clients over MCP servers.
Some MCP servers can perform additional processing before returning a response to the model. If we don’t control the MCP, that can make its behavior less predictable and may increase token usage if it returns verbose or poorly filtered output.
CLI clients are not automatically more predictable, but they often give us more control over filtering, formatting, and limiting the output. With well-designed wrappers, they can be more token-efficient than MCP tools that return verbose or poorly filtered responses.
Avoid vendor locking in your harness
Coding agents are still changing quickly, including their pricing models, capabilities, limits, and execution patterns. If we couple our harness primitives too tightly to a specific coding agent, we take on unnecessary risk. A better approach is to make the harness own the workflow and treat the coding agent as a replaceable execution engine.
Designing your workflow
There are already good open-source solutions that act as implementations of harness-based workflows, such as nWave or Superpowers. So why build our own custom harness?
Because the important part is not the harness itself, but how the team works together.
The example I usually use to explain why I think like this is Scrum adoption. We can adopt Scrum as purists, with all its ceremonies and practices, or we can take the practices that actually work for our context and adapt them to our needs.
The same applies here. At least for now, building a custom workflow is not especially complicated. Maybe in the future these primitives will become more complex, but today, adapting the harness to our own way of working gives us a lot of flexibility.
And don’t get me wrong, I think evaluating existing tools is both interesting and advisable. You can learn a lot from them, borrow good ideas, and use them as references. But the real value comes from understanding your own workflow well enough to design the right harness around it.
For example, imagine a typical software delivery flow with four broad stages: problem definition, discovery, implementation, and production.
We can design a harness around it. We can have shared skills for common tasks, repository-specific context for local constraints, and validation steps that help the team move work safely from one stage to the next.
In this example, the plugin acts as the shared layer. It packages reusable commands, MCP integrations, templates, rules for specific file types, hooks, agents, and skills that teams can reuse across repositories.
The repository-specific files provide the local product and architectural context the agent needs to understand the codebase, respect its constraints, and work safely within it.
As you can see, there is no magic here. A harness is mostly a combination of scripts, Markdown files, conventions, integrations, and automation points. But it can make a real difference when implementing new features.
Although this is a simplified example, I recommend starting with an even smaller setup and iterating as we discover what we actually need.
That is it for now. In the next article, I will focus on the harder part: the adoption of these tools in our teams.
