AI ENGINEERING / SEPTEMBER 2026 · 7 MIN READ
Multi Agent Systems as Software: a Control Checklist
“AI is software” is a useful provocation. If we take it seriously, we must hold our Multi Agent Systems to the same standards we expect from any service in production: typed interfaces, versioned artefacts, tests, controlled rollouts, and telemetry. In this piece I turn that mindset into a practical, developer-friendly checklist you can implement this quarter—without magical thinking.
IN THIS ARTICLE AI is software—so ship it like software · Typed tool contracts beat clever prompting · Prompts are artefacts: version them like code · Offline evals and canaries before human exposure · Deterministic paths in Multi Agent Systems · Telemetry-driven guardrails · What to do next: a concrete rollout plan
KEY TAKEAWAYS
01 Treat prompts, tools and policies as versioned code artefacts with typed contracts.
02 Build offline evals and canary gates before you expose agentic behaviour to users.
03 Force deterministic paths where it matters; use telemetry to adapt where it doesn’t.
AI is software—so ship it like software
I agree with the framing that AI is software and can be controlled. That doesn’t mean it’s trivial to bound; it means we should bring the same discipline we already use elsewhere. If a team built a payments service without typed contracts, versioned configs, tests, and rollout controls, we’d stop the release. The same standard should apply to our agentic systems.
In practical terms, that means treating model prompts, tools, policies, and orchestration as first-class artefacts. We should be able to answer: What’s the version of the prompt? Which tool schema did the agent call? What evaluation set did we pass before canary? What telemetry tells us whether the system is still within guardrails?
The rest of this article turns that mindset into a checklist. It’s aimed at developers who want tangible steps to make agentic behaviour reliable without strangling innovation. I focus on five levers: typed tool contracts, versioned prompts, offline evals and canaries, deterministic execution paths, and telemetry-driven guardrails. Throughout, I assume you may have multiple cooperating workers—Multi Agent Systems—rather than a single, monolithic agent.
None of this requires exotic infrastructure. It does require a bias towards explicitness over “let the model figure it out”. When in doubt, prefer a narrow, typed tool and an explicit planner over hoping temperature zero will save you.
Typed tool contracts beat clever prompting
Agent autonomy is seductive, but most failures come from loose tool interfaces. If a tool accepts a bag of strings, an LLM will eventually construct something invalid. The simplest fix is to make tools strongly typed, idempotent where possible, and small in surface area. You get debuggability and cacheability for free.
I use JSON Schema or a data model library to validate every tool invocation at the boundary. Reject early, log loudly, and avoid side effects until inputs pass validation. Tools should return clear result types: success payloads or structured error states, never ad‑hoc strings that the model must parse.
Provide crisp affordances. A tool should do one thing well with a stable contract. If you must evolve the schema, bump the version and keep the previous handler around for a deprecation window. Don’t silently change shapes out from under a running agent.
pythonfrom pydantic import BaseModel, Field, ValidationError
from typing import List
class WebSearchInput(BaseModel):
query: str = Field(..., min_length=3, max_length=128)
top_k: int = Field(3, ge=1, le=10)
class WebSearchResult(BaseModel):
urls: List[str]
# Idempotent, side-effect free; instrument inside this function, not the caller
def search_web(inp: WebSearchInput) -> WebSearchResult:
urls = [f"https://example.com?q={inp.query}&rank={i}" for i in range(1, inp.top_k + 1)]
return WebSearchResult(urls=urls)
# Boundary: validate tool call before execution
def handle_tool(payload: dict) -> WebSearchResult:
try:
inp = WebSearchInput.model_validate(payload)
except ValidationError as e:
raise ValueError(f"invalid tool input: {e}")
return search_web(inp)
If the agent generates invalid inputs, treat it as a model planning error and feed back a structured correction message, not a free-form essay. It’s easier to reason about a failing tool call than a meandering chain-of-thought. Typed contracts force the conversation into a safe lane.
AYAN’S TAKE
I don’t need my agents to be clever; I need them to be predictable, observable and cheap to change.
Prompts are artefacts: version them like code
Prompts are not sacred; they are configuration. Store them alongside code, give them immutable IDs, and wire them to models through explicit configuration rather than pasting blobs into source files. Small edits can cause large behavioural changes—treat those edits with change control.
Use a lightweight manifest that captures version, owner, inputs, and model hints. Default to deterministic settings (temperature 0) for system prompts and planning prompts. Keep domain knowledge out of prompts; put it in a retrievable knowledge base with explicit citations.
Every prompt should have table‑stakes tests: at least a few golden examples and negative cases. Tests don’t prove generality; they guard against regressions when you refactor. Pair them with a regression suite you can run locally and in CI.
yamlversion: 3
id: support_triage.en.v3
owner: ai-platform
inputs:
- ticket_text
- customer_tier
model_hints:
temperature: 0
max_tokens: 256
prompt: |
You are a concise support triage agent.
- If the issue is billing-related from Tier A, escalate immediately.
- Otherwise, summarise in one sentence and label a queue: {billing|tech|other}.
Respond as JSON: {"action":"escalate|route","queue":"billing|tech|other","summary":"..."}.
tests:
- name: refund-tier-a
inputs: {ticket_text: "I was double charged.", customer_tier: "A"}
expect_contains: '"action":"escalate"'
- name: tech-route
inputs: {ticket_text: "App crashes on login.", customer_tier: "B"}
expect_regex: '"queue":"tech"'
Keep versions immutable. If you need to tweak wording, publish a new version and let orchestration choose the variant via configuration flags. This makes rollbacks boring, which is exactly what we want.
Offline evals and canaries before human exposure
Offline evaluation is not about chasing a mythical single score; it’s about setting guardrails that catch regressions. Start with a small, curated set of tasks that reflect production realities: edge cases, common cases, and a handful of red‑lines you must never cross (e.g., sending an email to the wrong customer).
Evaluate behaviours, not vibes. For a triage agent, that might be exact match on an action field, regex on a queue label, and length caps on summaries. For a retrieval task, measure groundedness with citation checks and penalise hallucinated references. Keep metrics interpretable by the team that owns the system.
Once a candidate passes offline gates, perform a canary rollout: route a small, fixed slice of real traffic with feature flags and monitor leading indicators—error rates, tool validation failures, token costs per request, and latency. Define hard abort thresholds ahead of time to avoid debate when something goes sideways.
bash#!/usr/bin/env bash
set -euo pipefail
DATASET=${1:-datasets/triage_small.jsonl}
OUT=artifacts/preds.$(date +%Y%m%d%H%M%S).jsonl
mkdir -p artifacts
# Generate predictions deterministically for offline evals
python -m tools.generate_preds --data "$DATASET" --out "$OUT" --temperature 0
# Compare to gold with simple metrics; keep this repo-local and transparent
python -m evals.compare --gold datasets/gold.jsonl --pred "$OUT" --metrics exact,regex > "$OUT.metrics.json"
# Gate release on summary pass flag
jq -r '.summary' "$OUT.metrics.json"
if jq -e '.summary.pass == true' "$OUT.metrics.json" >/dev/null; then
echo "OK to canary"; exit 0
else
echo "Block release"; exit 1
fi
Canaries should be boring to run and easy to abort. The cheapest failure is the one you catch before users notice; the second cheapest is the one you roll back within minutes with no arguments about who decides.
Deterministic paths in Multi Agent Systems
When you coordinate multiple workers it’s tempting to allow free‑form back‑and‑forth. Resist that. Make the planner explicit. Use a finite state machine or a small DAG that defines legal transitions. Log every transition with reasons. You’ll still get flexibility—through tool outputs and retrieved context—but you’ll remove the worst kind of unpredictability: unbounded loops and invisible forks.
Determinism comes from three levers: structure, parameters, and time. Structure is your FSM or DAG. Parameters are the model settings—temperature 0 for planning and tool selection, with function‑calling or JSON mode to constrain output. Time is your budget: enforce step caps and deadlines, with a graceful failure path that returns partial work and telemetry pointers.
I prefer a single coordinator that calls tools and workers with typed contracts, using a deterministic planner. If you need parallelism, fan out deterministically and merge with explicit rules (e.g., prefer the earliest successful fetch, deduplicate by URL, cap to N items). Avoid emergent synchronisation based on unstructured messages.
typescripttype State = 'Read' | 'Search' | 'Draft' | 'Review' | 'Done' | 'Fail';
interface Ctx { goal: string; notes: string[]; attempts: number; }
const MAX_ATTEMPTS = 3;
function next(state: State, ctx: Ctx): State {
if (ctx.attempts > MAX_ATTEMPTS) return 'Fail';
switch (state) {
case 'Read': return ctx.goal.includes('research') ? 'Search' : 'Draft';
case 'Search': return ctx.notes.length > 0 ? 'Draft' : 'Fail';
case 'Draft': return 'Review';
case 'Review': return 'Done';
default: return 'Fail';
}
}
export function run(ctx: Ctx) {
let state: State = 'Read';
while (state !== 'Done' && state !== 'Fail') {
// All LLM calls use temperature 0 and JSON/function outputs; omitted here
if (state === 'Search') ctx.notes = ['https://example.com'];
if (state === 'Draft') ctx.notes.push('drafted');
if (state === 'Review') ctx.notes.push('reviewed');
ctx.attempts++;
state = next(state, ctx);
}
return { state, artifacts: ctx.notes };
}
Notice how the state machine expresses policy, not cleverness. If you need exploration, isolate it to sub‑steps where the blast radius is small, and cap it. You can still get creativity in content generation phases while keeping planning and tool selection on rails.
Telemetry-driven guardrails
Without telemetry, guardrails are vibes. With telemetry, they are feedback loops. Instrument at four layers: inputs (user requests and retrieved context), model choices (prompt ID, model name, temperature), tool calls (validated inputs, durations, side‑effects), and outputs (structured results, citations, token counts). Emit all of this as append‑only JSONL with request and session IDs.
Make the logs useful to both engineers and product owners. Aggregate dashboards should show failure reasons by category: tool validation errors, timeouts, policy blocks, and model formatting failures. Track token cost and latency as first‑class signals; they are often the earliest indicators of pathological prompts or bad retrieval.
Use the telemetry to drive guardrail decisions in near real time. Examples: block a prompt ID automatically if formatting failures spike; reduce max generation tokens for a route that exceeds cost budgets; switch to a safer fallback prompt when citations fall below a groundedness threshold; or disable a tool whose error rate climbs above a hard limit.
Keep the policy engine simple and transparent. A dozen clear rules beat a complex learned policy that nobody understands. When a rule fires, return a structured error to the caller with a pointer to a human‑readable runbook. The goal is controlled degradation, not silent failure.
What to do next: a concrete rollout plan
If you have an agent already in production, resist the urge to refactor everything at once. Pick one high‑value path and apply the checklist end‑to‑end; then extend laterally.
This week:
– Inventory your current artefacts: prompts, tools, retrieval sources, and orchestration. Write down versions (even if they start as v0) and owners.
– Wrap every tool call with a typed validator and return a structured result. Log rejected calls with enough context to reproduce.
– Set all planning and tool‑selection prompts to temperature 0. Constrain outputs to JSON or function‑calling formats where you can.
– Stand up a tiny offline eval set for your riskiest flow—five to ten goldens—then add a shell script that gates canary on pass/fail.
This month:
– Move prompts into a versioned manifest with tests. Publish a new version for any edits; stop in‑place mutations.
– Replace ad‑hoc orchestration with a small FSM or DAG. Define legal transitions and budget caps.
– Add telemetry: append‑only JSONL with request IDs, prompt IDs, tool calls, token counts, and final outcomes. Build a one‑page dashboard that breaks down failures by category.
This quarter:
– Expand offline evals; add negative tests and red‑lines. Automate canary with feature flags and pre‑agreed abort thresholds.
– Introduce a minimal policy engine that can block, downgrade, or fallback based on telemetry. Document each rule and how to override it in emergencies.
If you’d like a second pair of eyes on architecture and rollout, this is where a pragmatic Agentic AI Consultant earns their keep. The same applies if you want help shaping a platform that multiple teams can share; a seasoned Solution Architecture Consultant can save you months by aligning contracts, environments, and release practices.
If your team is earlier in the journey, start with a small discovery: catalogue tasks, define typed tools, draft initial prompts with manifest and tests, and run a first offline eval. Once that’s working, we can talk about scaling patterns, model choices, and vendor strategy through focused LLM Consulting Services.
Controllability isn’t an afterthought; it’s the product. Treat your agentic workflows as software, and the software will behave like software.
READ NEXT
AI ENGINEERING / THE PRACTICAL CTO
AI Adoption Strategy for Frontline Smart Glasses, 2026
Meta’s latest push has put AI smart glasses back on the agenda. Here’s a calm, practical brief for leaders deciding whether and how to pilot them with frontline teams in 2026.
AI ENGINEERING / THE PRACTICAL CTO
Microsoft’s New Copilot Is an AI Operating System for Work
Home, Code and Autopilot turn Copilot from an assistant into an AI operating system for work — with persistent agents, citizen-built software and consumption pricing. Here is what CTOs and boards should decide before it arrives.

