CourseModel Context Protocol · Module 10: Design Patterns and Agents · part 72 of 83
Part 72 · Module 10: Design Patterns and Agents

Topic 5: Module 10 lab

4 min read·22 Sept 2026

The lab combines the module in one script: a gateway in front of both servers, the eval run through the gateway, an orchestrator whose workers share the gateway, and the notes agent exposed as a server and called by an outer agent. It works on a temporary copy of the notes and writes a JSON report.

python
"""Module 10 lab: the patterns of this module, end to end, in one run.

1. An aggregator (gateway) fronts the notes and calendar servers.
2. The eval harness scores a single agent that reaches both through the gateway.
3. An orchestrator splits a compound question across concurrent workers that share the gateway.
4. The notes agent is exposed as an MCP server, and an outer agent calls it as a tool.
Everything runs in memory with the KeywordAgent stand-in; the JSON report lands in m10_lab_report.json.
"""
from __future__ import annotations

import json
import shutil
import tempfile
import time
from pathlib import Path

import anyio

from mcp import Client

from examples.m10_aggregator import Aggregator
from examples.m10_agent_server import build_agent_server
from examples.m10_calendar_server import build_calendar_server
from examples.m10_common import NOTES_DIR, KeywordAgent, estimate_tokens
from examples.m10_eval import CASES, run_eval, stand_in_factory
from examples.m10_orchestrator import QUESTION, split_stand_in
from notes_assistant.host import Host
from notes_assistant.server import build_server
from notes_assistant.store import NoteStore


def through_gateway(case: dict) -> dict:
    """The gateway renames notes__search_notes to gateway__notes_search_notes, and so on."""
    tool = case["expect_tool"]
    renamed = tool if tool == "none" else "gateway__" + tool.replace("__", "_")
    return {**case, "expect_tool": renamed}


async def main() -> None:
    work = Path(tempfile.mkdtemp(prefix="m10-lab-"))
    shutil.copytree(NOTES_DIR, work / "notes")  # the lab never touches the real notes folder
    store = NoteStore(work / "notes")
    report: dict = {}
    gateway = Aggregator({"notes": build_server(store), "calendar": build_calendar_server()})

    async with Client(gateway) as gw:
        specs = await Host({"gateway": gw}).load_tools()
        report["gateway_tools"] = [s["function"]["name"] for s in specs]
        report["definition_tokens_estimate"] = estimate_tokens(specs)[1]
        print(f"1. gateway exposes {len(specs)} tools, about {report['definition_tokens_estimate']} tokens")

        ev = await run_eval({"gateway": gw}, stand_in_factory, "keyword stand-in", [through_gateway(c) for c in CASES])
        report["eval"] = {k: ev[k] for k in ("cases", "selection_accuracy", "task_success", "selection_ci95")}
        print(f"2. eval through the gateway: selection {ev['selection_accuracy']:.3f}, success {ev['task_success']:.3f} "
              f"over {ev['cases']} cases")

        subs = split_stand_in(QUESTION)
        answers: dict[int, str] = {}

        async def work_on(i: int) -> None:
            answers[i] = (await Host({"gateway": gw}, chat_fn=KeywordAgent()).ask(subs[i])).text

        started = time.perf_counter()
        async with anyio.create_task_group() as tg:
            for i in range(len(subs)):
                tg.start_soon(work_on, i)
        report["orchestrator"] = [{"sub_question": subs[i], "answer": " ".join(answers[i].split())} for i in sorted(answers)]
        print(f"3. orchestrator: {len(subs)} workers finished in {(time.perf_counter() - started) * 1000:.0f} ms")
        for item in report["orchestrator"]:
            print(f"   - {item['sub_question']} => {item['answer'][:70]}")

    async with Client(build_agent_server()) as agent:
        outer = Host({"agent": agent}, chat_fn=KeywordAgent())
        result = await outer.ask("What do my notes say about spaced repetition before bed?")
        inner = json.loads(result.text)
        report["agent_as_server"] = {"outer_calls": [c.name for c in result.calls], "inner": inner}
        print(f"4. outer agent called {report['agent_as_server']['outer_calls']}; "
              f"inner agent used {inner['tools_used']}")
        print(f"   answer: {inner['answer']}")

    Path("m10_lab_report.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
    shutil.rmtree(work)
    print("report written to m10_lab_report.json")


if __name__ == "__main__":
    anyio.run(main)

Code explained

  • In simple words: the whole module as one dress rehearsal, with a report at the end.

The rest of this course is yours to keep

This course is bought on its own, once, and stays readable afterwards, including the parts added to it later.