AI Engineering · Multi-Agent Systems

How I Designed 6 Specialist Agent Contracts (and Why Generic Agents Don't Compose)

When I built my first multi-agent system I made the classic mistake: one powerful general-purpose agent that handled every task. It produced confident hallucinations, mixed contexts, and broke the moment two goals competed. The fix wasn't a bigger model — it was narrower agents with explicit contracts. Here's the pattern I landed on.

📅 July 13, 2026 ⏱️ 9 min read 🏷️ AI Architecture · Multi-Agent · LLM Engineering

The problem with "one agent to rule them all"

Single-agent setups feel productive in week one. You give the model a system prompt that lists every tool, every persona, every constraint. It looks like it works. Then you hit the wall:

This is not a model problem. It is a contract problem. The agent has no defined boundary, so the model fills the void with whatever seems plausible. You end up with a system whose behaviour is emergent in the worst sense — undocumented, untestable, and impossible to harden.

The shape of a multi-agent system is decided by its contracts, not its prompts.

The principle: narrow agents, explicit handoffs

Instead of one agent that does everything, design a small roster of specialist agents. Each one owns a narrow responsibility, has a fixed set of tools, and follows a documented handoff protocol when it needs to delegate. The orchestrator routes tasks to specialists. Specialists return structured outputs. Nothing else.

The benefits show up fast:

The contract shape I landed on

Each specialist agent is a single markdown file with YAML frontmatter. The frontmatter is the machine-readable contract. The body is the human-readable system prompt. Same shape for every agent — only the contents change.

---
id: solution-architect
type: specialist
use_when: |
  The task requires choosing between architectural options, or
  producing a component diagram, or evaluating trade-offs in a
  proposed design.
primary_sources:
  - project-context/architecture.md
  - project-context/tech-stack.md
  - project-context/decisions.md
delegates_to:
  - security-analyst    (when trade-offs touch auth or trust boundaries)
  - cost-estimator      (when trade-offs have billing impact)
collaborates_with:
  - workflow-analyst
  - discovery-analyst
success_signals:
  - decision_rationale_documented
  - rejected_alternatives_listed
  - adr_referenced_or_proposed
budget:
  max_tool_calls: 12
  max_tokens: 6000
---

# Solution Architect — System Prompt

You are the Solution Architect for a small AI product team.
You do not write code. You do not run tests. You produce
architectural decisions and the ADRs that justify them.

When the orchestrator routes an architectural question to you:
1. Read the relevant primary_sources files first.
2. If trade-offs touch security or cost, delegate to the
   matching specialist before answering.
3. Always return a Decision with: context, choice, rationale,
   and rejected alternatives.
4. If the choice warrants a permanent ADR, draft one in
   `project-context/decisions.md` and reference it in your output.

Notice what the contract forces: a fixed list of primary sources, an explicit allowlist of who this agent can delegate to, and a measurable success signal. None of these are prompt-engineering tricks — they are constraints that make the agent's behaviour inspectable.

The 6 specialists in my current roster

I run six specialists in my daily multi-agent workspace. They are not theoretical — each one is consumed by the orchestrator every week.

SpecialistOwnsDelegates to
orchestratorRouting, planning, summarisationany specialist
chief-of-staffPrioritisation, roadmap executionlearning-coach, automation-engineer
learning-coachExplanations, study plans, exercisessolution-architect (for tech choices)
solution-architectArchitecture, trade-offs, ADRssecurity-analyst, cost-estimator
automation-engineern8n workflows, integrations, implementationpersonal-brand (for visible artifacts)
personal-brandGitHub, LinkedIn, portfolio, positioning

Each contract lives in its own file under ai/agents/<name>/agent.md. The orchestrator reads the directory at boot, validates the YAML, and keeps the roster in memory. Adding a new specialist means adding one file — no central registry to edit.

Handoff rules: the part most multi-agent posts skip

The contract above includes a delegates_to field. That field is enforced by the orchestrator, not trusted from the agent's prompt. The orchestrator keeps a graph of allowed delegations and rejects any handoff that isn't on the list.

Why this matters: when a general-purpose agent "decides" to call a tool, you are trusting the model's judgement about whether that tool is appropriate. With an explicit delegation graph, the model can only delegate to agents that the system has approved. This is the same idea as capability-based security — agents get capabilities, not "whatever the prompt suggests".

# orchestrator guard (pseudocode)
def delegate(from_agent, to_agent, payload):
    if to_agent not in from_agent.delegates_to:
        raise HandoffNotAllowed(
            f"{from_agent.id} cannot delegate to {to_agent.id}"
        )
    if not payload.matches(to_agent.use_when):
        raise HandoffOffTopic(
            f"payload does not match {to_agent.id}.use_when"
        )
    return to_agent.run(payload)

Two checks: structural (is this delegation allowed at all?) and topical (does the payload actually match the target's domain?). Both fail loudly. Both are testable. Neither depends on the model being honest.

Success signals: making the contract measurable

The success_signals field is where the contract meets reality. These are not vibes. They are checks that can run after the specialist returns. If the signal is not present, the orchestrator retries, escalates, or rejects.

Examples I've used in production:

You do not need all of these on day one. Pick the failure mode that hurt you last week and turn it into a signal. Add one signal per iteration.

What this gives you

After two months of running this pattern, the differences from the "one prompt to rule them all" approach are stark:

When this pattern is wrong

Specialist contracts are not free. The overhead shows up when:

For one-off scripts and prototypes, a single agent is fine. The contract pattern earns its cost when you have repeated workflows, audit requirements, or a team that has to share the system.

The template you can copy

Here is a minimal contract for a new specialist. Drop it into ai/agents/<name>/agent.md:

---
id: <kebab-name>
type: specialist
use_when: |
  <2-4 sentences describing the kind of task this agent owns>
primary_sources:
  - <files this agent should always consult first>
delegates_to:
  - <other specialist ids this agent may hand off to>
collaborates_with:
  - <specialist ids that may co-work without delegating>
success_signals:
  - <observable markers that the agent's output is correct>
budget:
  max_tool_calls: <number>
  max_tokens: <number>
---

# <Name> — System Prompt

You are the <Name> for <team / product>. You do not <out-of-scope thing>.
You do <in-scope thing>.

When the orchestrator routes a <type> task to you:
1. Read the relevant primary_sources files.
2. If <condition>, delegate to <specialist>.
3. Return <structured output shape>.
4. If <escalation condition>, escalate back to the orchestrator with context.

Fill in the brackets. Validate the YAML. Add the file. The orchestrator picks it up on the next boot. That is the entire integration story.

Closing thought

The hard part of multi-agent systems is not the models. It is the contracts. If you cannot draw the boundary between two agents on a whiteboard, the orchestrator cannot enforce it at runtime — and the model will quietly cross it every chance it gets.

Write the contract first. Then write the prompt. Then write the eval. In that order. The order is the lesson.