Developing Agents: From Theory to Practice
Practical patterns for building reliable agent packs with clear roles, context boundaries, quality gates, and software-style development discipline.
Executive Summary
My first agent was a single prompt file: about fifty lines of instructions for writing a product specification. It worked well enough on the first try that I thought I was done. That file eventually turned into a multi-agent system with five specialized agents, five reusable skills, explicit permissions, and separate context boundaries. Building that agent pack — a packaged collection of agents, skills, prompts, and instructions — changed how I think about agents: they are code written in natural language, and they need the same engineering habits as code.
This post is the set of lessons I wish I had at the start: what belongs in a skill versus an agent, when to split work across multiple agents, how to manage context, what makes an agent durable, and how an agent factory can build other agents. The examples come from production agent packs, so the emphasis is not only what works, but where the failure modes show up.
The Building Blocks: Agents, Skills, Prompts, and Instructions
Before architecture, the vocabulary has to be clear. Most agent systems I build use four building blocks, and the first design decision is choosing which one fits the job.
Skills: Reusable Capabilities
A skill is a capability that more than one agent may need: converting a Word document to Markdown, writing error messages in a product's content voice, querying a knowledge base, or applying a house style to a draft. Skills are closer to utility libraries than workers. Any agent can load one when the task calls for it.
A skill can be technical, like a docx-to-markdown skill that wraps a CLI workflow, or domain-specific, like an executive-writing-style skill that sets tone, structure, and readability rules for decision makers. It is still a skill because it has no persona, no independent goal, and no ownership of the workflow. It is a packaged capability.
Agents: Task-Oriented Workflow Entities
An agent is something you assign work to. It has a role, a goal, a process, and a definition of done.
Use an agent when the task requires coordination, judgment, or multi-step reasoning. A specification-writing agent that gathers requirements, structures them into sections, validates completeness, and produces a formatted output is an agent. It manages context, loads skills, and verifies its own work.
The difference matters because agents run in their own context. When invoked, an agent gets a fresh context window, its own tools, and its own reasoning space. That isolation is one of the main reasons agent systems can scale beyond a single long prompt.
Prompts and Workflows: Reusable Invocation Patterns
Prompts, workflows, and slash commands are reusable ways to invoke agents. A prompt may gather context first by querying an MCP server for customer feedback, extracting snippets from a chat history, or pulling prior specifications from a repository. Or it may simply call the right agent with a standard template so the user does not have to remember the exact format.
That separation keeps the agent reusable. The agent stays generic; the prompt decides how to call it. Several prompts can invoke the same specification agent with different context or templates, while the agent's core behavior stays consistent.
Instructions Files: Personalizing Agents to Repositories
The final building block is instruction files like copilot-instructions.md or agents.md. These are repository-level configuration files that personalize generic agents for a specific project.
A generic specification agent does not know that a team stores specifications in /specifications/ or uses a feature-specific filename prefix. Hardcoding those details makes the agent less reusable. Asking the user to repeat them in every prompt is tedious. Put them in an instructions file and let the agent pick them up from the repository.
Instruction files belong to the repository, not the agent. They bridge a reusable agent and a team's local conventions: output locations, naming rules, glossary terms, architectural context. Keep the agent's logic in the agent and the project's conventions in the repository.
When to Use Which
A useful decision framework:
| Building Block | Use When... |
|---|---|
| Skills | A generic, reusable capability is needed — something any agent might want to do |
| Agents | The task needs a persona with a workflow, multi-step reasoning, or its own context |
| Prompts | A reusable invocation pattern is needed — a predefined way to trigger an agent with or without pre-assembled context |
| Instruction files | A generic agent needs to be personalized for a specific repository or team |
Agent Packs: Designing for Multiple Agents
What Is an Agent Pack
An agent pack is a packaged collection of agents, skills, prompts, and hooks that work together on a larger workflow. It is how an agent system gets distributed, the same way a library or package distributes code.
A concrete example: my product-brief agent pack contains five agents (an orchestrator, a researcher, an evidence analyst, a strategy modeler, and a composer), five skills (product-brief-framework, evidence-integrity, decision-metrics-financials, executive-writing-style, and stakeholder-psychology), and a set of instructions. Each agent has a distinct role, distinct permissions, and distinct context boundaries. Together, they take raw inputs — meeting notes, transcripts, documents — and produce a decision-grade product brief.
Another pack, the copilot-factory, contains agents for designing and building other agent packs. That pattern — using agents to build agents — is now part of my normal development loop.
Why Multiple Agents
For complex workflows, multi-agent designs have beaten monolithic ones in my work. A single agent can research, write, verify, format, and publish simple outputs. Once the task gets larger, splitting the work across focused agents improves both reliability and debuggability.
Context isolation. Each agent runs in its own context window. If one researcher investigates competitor A and another investigates competitor B, the findings stay separate until the orchestrator collects them. The result is two independent assessments instead of one long analysis where details from one subject bleed into the next. It is the same reason modular code is easier to reason about: separate concerns stay separate.
Constructive opposition. One of the most powerful patterns in agent architecture is having one agent create and a separate agent verify. The creator drafts an artifact. A critic agent — running in its own context, without access to the creator's reasoning — reviews the output against defined criteria and reports what passes and what fails.
The verifier should not inherit the creator's assumptions. If the same agent writes a specification and then reviews it, it carries its own blind spots into the review. A separate agent sees only the output and the criteria. That is why code review works: the reviewer has not been staring at the implementation for hours.
In the product-brief pack, the orchestrator delegates drafting to the composer and then runs a separate verification process. The orchestrator has not processed the source material the way the composer has. It evaluates the brief on its own terms.
Parallelization. Three competing products can be researched by three sub-agents at the same time. Each agent keeps its full context capacity for one subject instead of sharing one increasingly crowded window across all three. The workflow is faster, and the notes are cleaner.
The Orchestrator Pattern and Permission Scoping
The orchestrator is the coordinator. It receives the overall task, breaks it into subtasks, assigns each to the appropriate specialized agent, collects results, and manages the flow. Critically, it does not do the work itself.
Orchestrators work best with narrow permissions. In the agent factory pack, the factory orchestrator can read files and delegate to other agents. That is it. It cannot write code, search the web, or execute commands. The constraint is partly about safety, but it also shapes behavior. If the orchestrator could do everything, it would be tempted to skip delegation, fill its context window, and produce weaker work.
The same rule applies across the pack. A critic agent with write access may start fixing issues instead of reporting them. A research agent with access to the output directory may bypass the orchestrator and publish a final artifact. Give each agent only the capabilities its role needs.
In the product-brief pack, the orchestrator reads and writes session state and delegates. The research runner executes commands and fetches URLs but writes only to a scratch directory. The composer writes the final brief. Each agent has exactly the permissions its role requires and no more.
Not every agent harness supports fine-grained permissions. Roo Code can restrict file reads and writes to path patterns. GitHub Copilot does not have the same built-in file boundary controls. Hooks help close that gap. A preToolUse hook runs before a tool call, inspects the file or command the agent wants to use, and approves or blocks it. Hooks give you deterministic permission checks even when the harness itself is more permissive.
Context Engineering: The Central Challenge
Prompt wording matters less than context quality. The agent can only reason over what it can see, and its output degrades when that context is noisy, stale, too broad, or missing the critical constraint. Context engineering is the work of deciding what the agent sees, when it sees it, and what stays out.
Larger Context Windows Do Not Solve the Problem
Large context windows do not remove the problem. Output quality can decline long before the window is technically full.
As the context grows, the model has more material to weigh. Specific constraints, early decisions, exact numbers, and important phrasing become easier to lose. Compaction helps make room, but it is not lossless. You get plausible output that misses a requirement stated clearly earlier in the conversation.
Push and Pull: A Context Management Boundary
A useful boundary is push versus pull: some agents should receive pre-assembled context, while others should discover context themselves.
Agents that benefit from pushed context include synthesizers, writers, critics, and formatters. Their job is reasoning and production, so gather the context before invocation. Let the prompt or workflow layer collect the inputs, then give the agent a clean packet to work from instead of a trail of search results and file listings.
Agents that need to pull context include researchers, analyzers, and any agent responsible for discovery. Give them tools to search the web, query databases, and scan repositories. Their job is to find the material, not to polish the final artifact.
The design pattern is to separate those jobs. Researchers pull context and save findings to shared memory. The synthesizer receives those findings as pushed context in a fresh window. The researcher's messy discovery trail stays out of the composer's production context.
In the product-brief pack, the research runner pulls context — it fetches URLs, executes commands, searches the web. It writes its findings to a scratch directory. The brief composer then receives those findings as clean, pre-assembled input. The composer never searches for anything. It reasons and writes.
Short-Term and Long-Term Memory
Agent memory has two tiers. Design both deliberately.
Short-term memory holds intermediate results during a multi-step task — research notes being synthesized, partial drafts being assembled, verification results being collected. This memory exists for the duration of the task and is discarded afterward.
Long-term memory persists across runs. A competitive analysis agent that stores snapshots of its comparisons enables future runs to track how a product's positioning changes over time. A feedback analysis agent that accumulates customer sentiment data creates a longitudinal record that grows more valuable with each execution.
A good agent knows where short-term state goes, where long-term output persists, and how future runs find it. Without that design, information disappears in subtle ways, and downstream artifacts get worse without an obvious error.
The Filesystem as Memory
The most useful memory mechanism I have found is the filesystem. Session directories, progress files, per-agent artifact folders, and archives of completed runs give agents durable memory that survives context compaction.
It is not elegant, but it is dependable. In-context memory disappears when the window resets; files stay. An agent can write intermediate results to a scratch directory, compact its context, and read the results back later. Because the memory is just files, a human can inspect it, debug it, and understand what the agent actually saved.
Sub-Agents as a Context Strategy
Breaking large tasks into sub-agents is also a context strategy. Each sub-agent gets a fresh window. A research task split across three sub-agents gets three full context windows instead of one overloaded one.
Consider researching five competitors. One agent can research all five sequentially, accumulating findings in an increasingly bloated context where information from competitor A starts contaminating the analysis of competitor E. Or five agents can each research one competitor in isolation, with full context capacity, and the orchestrator synthesizes the results.
The second approach is usually better. The orchestrator works from summaries instead of raw sprawl, and each research agent keeps its attention on one subject. That directly addresses context overflow, the failure mode that breaks many complex agent workflows.
What Makes a Good Agent
The characteristics of a good agent came from debugging bad ones: agents that drifted, outputs that changed shape between runs, context that disappeared without warning. The agents that survived had the same traits.
Reusable and Generic Within Its Purpose
A good agent solves a specific problem without being locked to one narrow variant of that problem. A specification-writing agent can write any specification, not just specifications for one business unit or one feature type. The purpose is clear and bounded, but the implementation is generic enough to handle the full range of inputs that purpose implies.
An agent that only works for one scenario is closer to a macro than an agent. The reusable part should stay generic within a clear scope. Project-specific details belong in prompts and instruction files, not in the agent definition.
Easy to Use
When triggered incorrectly or with missing inputs, a good agent explains how to use it, the way a CLI prints usage instructions when invoked without arguments. That is not polish. If an agent fails silently or produces junk on bad input, users stop trusting it. Every agent should handle incorrect invocation explicitly.
Self-Verifying
A good agent has built-in verification. Where appropriate, it checks its own work — validating that a generated spec includes all required sections, that a research summary actually references the sources it was given, that a produced artifact conforms to its output template. Verification is part of the agent, not something bolted on afterward.
Agent output is less predictable than deterministic software. The model may miss a section, invent a reference, or produce something that looks right but does not match the template. Self-verification catches the obvious failures early. A separate verifier or critic catches more, because it reviews without the author's assumptions.
Context-Aware
All agents operate inside a system with memory, context windows, and compaction. A good agent understands this and manages its context explicitly. For simple tasks, this means being aware that long conversations degrade quality. For complex workflows, it means the agent has a defined strategy for what information it carries forward, what it stores externally, and what it discards.
Memory design has to be concrete. The instructions should say where intermediate results go, what format they use, and when information moves from short-term to long-term storage. Agents that ignore context management often work on small tasks and fail quietly on large ones. The output looks plausible, but the model has lost an earlier constraint.
Explicit in Everything
A good agent has an explicit answer for every operational question: What are its inputs? What are its outputs? Where does intermediate state go? What persists after the run ends? What does the output look like?
Outputs should follow a template. A brief should have named sections in a defined order. A competitive analysis should produce a structured comparison. A spec should conform to a known schema. Explicitness is not rigidity; it is what lets downstream agents and humans depend on the output.
The Goldilocks Zone
There is a tension in agent design between specificity and freedom. Too many instructions constrain the model's reasoning and produce rigid, non-creative output. Too few instructions produce unpredictable results that miss requirements.
The useful zone is narrow: enough structure to get the result, enough freedom for the model to reason. Define the goal, constraints, output format, and acceptance criteria. Avoid scripting every intermediate step. A good agent with clear boundaries can find paths a rigid script would miss.
One test I use: run the agent on comparable models. If it works on one capable model and falls apart on another, the instructions are probably too implicit. Model portability is a useful proxy for instruction quality.
Developing Agents Like Software
Early on, I treated agents casually: edit a prompt, try it, tweak it, move on. They worked until they did not. Then there were no tests, no versioning, and no way to understand what changed. Treating agent development like software development made the work less mysterious and the agents more reliable.
The Agent Factory: Using Agents to Build Agents
Use agents to build agents. If agents are natural-language code, LLMs are useful for drafting, reviewing, and refining that code. I ended up building an agent factory: a pack whose job is to design and implement other packs.
LLMs are fast at prompt work. Agent definitions are mostly natural-language instructions: goals, constraints, workflows, output templates. The human still supplies judgment about purpose, quality, and boundaries. The model speeds up the drafting loop by producing and comparing multiple formulations quickly.
Agents also tend to know the conventions of the harness they run in. An agent running inside GitHub Copilot has seen YAML frontmatter, tool-calling conventions, permission models, and common agent-pack file structures. It can produce files that fit those conventions with less trial and error than a human starting from docs alone.
Constructive opposition applies here too. One agent designing and implementing its own work will miss things. Separate roles force disagreement into the process: a factory architect designs the topology, a factory engineer writes the agent definitions and skills, and a factory critic reviews the result against quality criteria from a separate context. The critic sees what the architect and engineer normalized.
Scale is the other reason. If I build five agent packs by hand, I get five sets of prompts, five configurations, and five places for conventions to drift. A factory centralizes those standards. When the factory improves, every future pack inherits the improvement because the process carries it forward.
I built an agent factory with that structure: orchestrator, architect, engineer, and critic. The first version started from a spec and was written by an LLM, including the factory itself. Once it existed, I used the factory to improve its own agents and skills. Building a new pack went from a multi-day manual effort to a guided session where the developer provides requirements and the factory produces the implementation.
Dedicated Development Location
For shared agent packs, a dedicated repository works better than embedding agents in every repository they operate on. Shared libraries live in one place because they evolve together. Agent packs are the same: they evolve around models, tooling, and practice, not around one product codebase.
Copy an agent into three project repositories and you now have three versions. They will drift. A dedicated agent repository with a distribution mechanism — an installer, a marketplace, or a package manager — keeps one canonical version and gives every installation a path to updates.
Separation also keeps the lifecycle clean. Agents need their own tests, reviews, and releases. If they live inside a product repository, their changes get tangled with product changes, their tests run alongside product tests, and their release cadence depends on something unrelated.
Quality Gates
Build quality gates into every agent workflow: checkpoints where output must pass defined criteria before the workflow advances. Use critic agents that return PASS or BLOCKING verdicts, orchestrators that require explicit human approval before the next phase, and retry limits that stop infinite repair loops.
Without quality gates, the workflow relies on the final output looking right, and agent output is good at looking right. A draft can miss a constraint, a research summary can invent a source, and a specification can skip a required section while still reading well. Quality gates force the workflow to stop and verify before moving forward.
Review, Package, Deploy
Agent changes should go through pull requests. Natural-language instructions can be ambiguous in ways the author misses: a constraint that can be read two ways, a permission boundary that contradicts another section, a workflow step that assumes hidden context. PR review catches those issues before users do.
Once reviewed and merged, agents can be packaged for distribution. Many harnesses support plugin marketplaces: repositories with standard structures that users install into working projects. The pattern is simple: develop in one place, publish through a channel, install where needed.
When an agent pack is installed from a marketplace, updates flow through the same channel. You update the source, and every installation picks up the change — no one copies files between repositories, no one maintains multiple versions.
Plan for Continuous Iteration
Agents can age quickly. Models improve, tools change, workflows shift. An agent that was a good design in March may need revision by June, not because it broke, but because better options became available. Build agents that are easy to update, test, and replace. The goal is not a perfect agent; it is a process that keeps producing better ones.
Additional Considerations
A few tradeoffs show up repeatedly when designing agent systems.
Token cost of multi-agent orchestration. Multi-agent workflows consume more tokens than single-agent approaches. Each sub-agent receives its own system prompt, its own context, and produces its own output. An orchestrator that delegates to five agents uses roughly five times the baseline token cost of a single agent. Pay that cost when the task needs context isolation, parallel work, or independent critique. For straightforward tasks, a single agent is often enough.
Over-engineering. More instructions do not automatically produce better agents. Past a threshold, extra rules create contradictions and degrade output quality. When an agent misbehaves, simplify and clarify before adding more guardrails. The model has to interpret every instruction you give it.
Context overflow at synthesis points. Multi-agent systems that pass large artifacts between agents can still exceed context limits where results are combined. The orchestrator that collects findings from five research agents may itself run out of useful context when trying to synthesize them. Design for summarization at handoff boundaries — each agent produces a focused summary, not a raw data dump.
Model dependency. An agent tuned to the quirks of one specific model will break when that model is updated or when the organization switches providers. Explicit, clear instructions with defined constraints are more portable than implicit patterns that rely on a model's undocumented tendencies.
Build Your First Agent Pack
Agents are software written in natural language, so build them with software habits. Keep shared packs in a dedicated development location. Use an agent factory to bootstrap new packs with a consistent structure. Give each agent a distinct role, permission set, and context boundary. Define memory explicitly. Separate agents that pull context from agents that receive it. Test with constructive opposition. Review through pull requests. Package for distribution. Iterate continuously.
The tools will keep changing — this is an April 2026 snapshot, and the ecosystem moves fast. The durable part is the engineering discipline. That is what separates an agent that works once from one that works reliably, and a personal workflow from a pack a team can depend on.