Building Your Own Agents
From using agents to making them. Every major agent platform — GitHub Copilot, Anthropic’s Claude, OpenAI’s ChatGPT, and Google’s Gemini — gives you the same ladder of effort, from “write some Markdown” to “embed a full agent runtime in your app.” This page walks the rungs concretely, naming each vendor’s equivalent as we go, because the art is the same everywhere: pick the lowest rung that solves your problem.
There are easy and hard ways to build your own helper — from writing a simple recipe card, to handing it a toolbox, to building the whole robot yourself. You pick based on how much you actually need.
The four ways to build an agent
☺ Like you’re 10: Think of building a robot helper like choosing LEGO sets: sometimes you just write it a recipe card, sometimes you snap a few pieces onto a ready-made robot, and sometimes you build the whole thing from a giant box — each way is more work but gives you more control.
Choose by asking: who is this for, and how much control do I need? Each rung has a near-identical equivalent on every platform:
- ① A repeatable specialized procedure in your repo? → a Skill / Markdown procedure. Lowest effort, just Markdown — a
SKILL.mdorCLAUDE.mdfor Claude, a Copilot Agent Skill (SKILL.md), a custom-instructions / project file for ChatGPT and Gemini. - ② A specialized version of the assistant (a “frontend agent” with specific tools/instructions) for your team? → a configured / custom agent — a GitHub Copilot Custom Agent, a saved Claude configuration, an OpenAI Custom GPT, or a Gemini Gem.
- ③ Expose an external tool/service inside the assistant for many users (
@your-service do X)? → a connector / Extension or MCP server — a GitHub Copilot Extension (a GitHub App), an MCP server for Claude, an OpenAI connector, or a Gemini extension. - ④ Build your own application or product with an embedded agent? → an agent SDK — the GitHub Copilot SDK, the Claude Agent SDK, or the OpenAI Agents SDK.
Worked example: which path?
☺ Like you’re 10: It’s like picking the right tool for a chore — you don’t grab a bulldozer to plant a single seed. You look at what you actually need to do, then pick the smallest tool that gets it done.
The map isn’t abstract — match the need to the rung. The rung is the decision; the product name just depends on which assistant your team already uses:
- “Everyone on my team should be able to ask our assistant about our Jira tickets.” → rung ③, connector / MCP server: hand it the Jira API endpoints; no AI code to write. That’s a Copilot Extension (skillset), an MCP server for Claude, or an OpenAI/Gemini connector.
- “Our assistant keeps missing our deployment conventions.” → rung ②, a configured / custom agent with your instructions and MCP servers — a Copilot Custom Agent, a saved Claude config, a Custom GPT, or a Gemini Gem.
- “I want a code-review bot running headless in our own CI, on our infra.” → rung ④, an agent SDK: embed the runtime exactly where you need it — the Copilot SDK, the Claude Agent SDK, or the OpenAI Agents SDK.
- “Our assistant should always run our migration checklist the same way.” → rung ①, a Skill / Markdown procedure — a
SKILL.mdorCLAUDE.md. Start here whenever Markdown is enough.
The decision is about the need, not the vendor: the same four rungs exist on GitHub Copilot, Claude, ChatGPT, and Gemini, so pick the rung first and the product falls out of whichever platform you’re on.
Rule of thumb: climb only as high as the need requires. Most jobs are a Skill or an Extension; reach for the SDK when you genuinely need a custom product.
Foxy: If the SDK is the most powerful, shouldn’t I just build everything with it?
Professor Owl: Power isn’t the goal — fit is. It’s a ladder: a Skill in Markdown, then a config or Custom Agent, then an MCP server or Extension, and only at the top, the SDK.
Benny the Beaver: I always try the bottom rung first. If a recipe card fixes it, I don’t haul out the whole workshop.
Timmy the Turtle: Let me check that plan — you only need the SDK when you’re shipping a real product. For your team’s Jira lookup, an Extension is plenty.
Foxy: Got it — climb only as high as the need requires!
Rung ③ up close — connectors: the lightweight vs. full-control split
☺ Like you’re 10: Imagine adding a new gadget to a walkie-talkie you already own: the easy way is to just tell it the phone number to dial and let the walkie-talkie do the talking, and the hard way is to wire up the whole conversation yourself. Every chat helper — Copilot, Claude, ChatGPT, Gemini — lets you plug in gadgets the same two ways.
Rung ③ plugs an external capability into your assistant, usually invoked with an @mention. On GitHub Copilot that’s a Copilot Extension built as a GitHub App; with Claude it’s an MCP server (local stdio or remote HTTP); ChatGPT and Gemini expose the same idea through connectors/extensions. Whatever the label, there are two construction styles, and choosing between them is the key decision — we’ll use Copilot’s two flavors as the concrete illustration because they name the split cleanly:
The lightweight path (start here)
You define a small set of API endpoints (“skills”) — up to 5 in a Copilot skillset. The assistant handles everything AI: the prompt engineering, deciding when to call your endpoint, and formatting the response. You provide just the API; you need no AI expertise. This is the same bargain an MCP server strikes with Claude or a connector with ChatGPT/Gemini — you describe a tool, the model decides when to use it. Perfect for straightforward integrations — data retrieval and basic actions. An endpoint is defined by a name, an inference description (so the model knows when to call it), a URL, and a parameters schema:
Name: random_commit_message
Description: Generates a random commit message
URL: https://your-host/random-commit-message
Parameters: { "type": "object" }
Return type: StringOnce registered, users @your-skillset in Copilot Chat and the assistant orchestrates calls to your endpoints automatically. The equivalent lightweight setup with Claude is registering an MCP server that advertises the same tool.
The full-control path
You control the entire interaction flow: custom prompt crafting, your own logic, and you can pick the specific LLM — for example one of Anthropic’s Claude models, an OpenAI GPT model, or a Google Gemini model. More work, more power. Use when you need complex multi-step workflows, custom reasoning, or a particular model. GitHub provides a preview SDK for agent-style Extensions (handling request verification, response formatting, and API interactions); with Claude you’d write a richer MCP server or reach for the Claude Agent SDK, and OpenAI offers the same via its Agents SDK.
Rung ④ up close — an agent SDK embeds the runtime in your app
☺ Like you’re 10: Building an agent from scratch is like building a car engine before you can drive anywhere — tons of work before you even start. An agent SDK hands you the finished engine so you can just build your car around it and go.
This is the most powerful builder. The insight behind it: building agentic workflows from scratch is genuinely hard — you have to manage context across turns, orchestrate tools, route between models, integrate MCP servers, and handle permissions, safety boundaries, and failure modes. Before you reach your product logic, you’ve built a whole platform.
Every major vendor now ships a library that removes this burden — Anthropic’s Claude Agent SDK, OpenAI’s Agents SDK, and GitHub’s Copilot SDK; Google exposes similar building blocks through its Gemini/Vertex agent tooling. The common idea: they expose a production-tested agentic runtime as a library you call from your own code, so you get the planning loop, multi-turn execution, tool invocation, model routing, MCP support, authentication, and streaming — and build on top instead of reinventing. The example below uses the GitHub Copilot SDK (the one we’ll use in the capstone) as the concrete walk-through; the Claude Agent SDK and OpenAI Agents SDK follow the same shape. In Copilot’s case the runtime is the same one that powers Copilot CLI.
What it gives you
Here are the Copilot SDK’s features; the Claude Agent SDK and OpenAI Agents SDK expose a near-identical feature set under their own names.
- The same agentic loop behind Copilot CLI (plan → call tools → observe → iterate).
- Multiple languages: Node.js/TypeScript, Python, Go, .NET, Rust, and Java (GA).
- Custom tools, custom agents, and Agent Skills you define.
- MCP server integration (local stdio and remote HTTP).
- Sessions (multi-turn memory), streaming responses, and a permission handler so your app approves/denies/customizes each tool call.
- Auth options: use your existing Copilot subscription, or BYOK (Bring Your Own Key) with providers like OpenAI, Azure AI Foundry, or Anthropic.
How it works architecturally
The SDK talks to the Copilot CLI running in server mode over JSON-RPC; it manages that process lifecycle for you:
A first taste — pick your SDK
☺ Like you’re 10: Same tiny recipe in every kitchen — switch the helper on and ask it something. Pick a helper below to see its exact starter code.
The “make an agent” boilerplate is a handful of lines in every SDK: start the runtime, run a prompt. Pick yours:
@github/copilot-sdknpm i @github/copilot-sdk
import { CopilotClient, approveAll } from "@github/copilot-sdk";
const client = new CopilotClient();
await client.start(); // manages the Copilot CLI process over JSON-RPC
const session = await client.createSession({ model: "gpt-5", onPermissionRequest: approveAll });
const event = await session.sendAndWait({ prompt: "Hello, world!" });
console.log(event.data.content);
await client.stop();Confirm method names against the GitHub Copilot SDK's current docs — true at time of writing.
@anthropic-ai/claude-agent-sdknpm i @anthropic-ai/claude-agent-sdk
import { query } from "@anthropic-ai/claude-agent-sdk";
// Reads ANTHROPIC_API_KEY from the env (or an existing Claude Code login).
for await (const message of query({
prompt: "Hello, world!",
options: { model: "claude-opus-4-8" },
})) {
if (message.type === "assistant") console.log(message.message.content);
}Confirm method names against the Claude Agent SDK's current docs — true at time of writing.
@openai/agentsnpm i @openai/agents
import { Agent, run } from "@openai/agents";
// Reads OPENAI_API_KEY from the environment
const agent = new Agent({
name: "Assistant",
instructions: "You are a helpful assistant.",
model: "gpt-4.1",
});
const result = await run(agent, "Hello, world!");
console.log(result.finalOutput);Confirm method names against the OpenAI Agents SDK's current docs — true at time of writing.
@google/genainpm i @google/genai
import { GoogleGenAI } from "@google/genai";
// Reads GEMINI_API_KEY from the environment.
const ai = new GoogleGenAI({});
const res = await ai.models.generateContent({
model: "gemini-flash-latest", // model names true at time of writing
contents: "Hello, world!",
});
console.log(res.text);
// Agentic use: pass `config.tools` with function declarations; the SDK runs the tool loop.
// Full agent orchestration lives in Vertex AI Agent tooling (e.g. Agent Development Kit).Confirm method names against the Google Gemini SDK's current docs — true at time of writing.
That’s the entire “make an agent” boilerplate — the planner, tool loop, and runtime are handled. You spend your effort on your tools and your product.
SDK usage is billed by your provider — Copilot SDK prompts count toward your premium-request quota (unless you use BYOK), while the Claude, OpenAI, and Gemini SDKs bill per-token API usage. Budget for production traffic, and confirm exact method names against each SDK’s current docs — these libraries matured quickly through 2026.
What a real agent project looks like
☺ Like you’re 10: A tidy project is like a well-organized bedroom where socks, books, and toys each have their own drawer — when everything has its own place, you can find and fix things fast instead of digging through one giant messy pile.
A single script is fine to start with — but a capable agent still needs a clean codebase. Once reasoning, tools, models, prompts, memory, an API, and tests all pile into one file, even a promising prototype gets hard to debug, secure, and scale. Pulling those concerns into their own homes is exactly what the layout below buys you — use it as a scaffold, and notice that nearly every folder maps to something you’ve already met in this course.
ai-agent-project/ ├── README.md # what it is, setup, usage, examples ├── requirements.txt # pinned Python dependencies ├── .env # API keys & secrets — never commit this ├── .gitignore # files Git should skip (including .env) ├── docker-compose.yml # optional: DB, Redis, a vector store, … ├── src/ │ ├── agent/ # the core loop, executor, state, memory │ │ ├── __init__.py │ │ ├── agent.py # the reason → act → observe loop │ │ ├── executor.py # runs the tool calls the model chooses │ │ ├── state.py # tracks the run: history, stop condition │ │ └── memory.py │ ├── tools/ # one module per tool the agent can call │ │ ├── __init__.py │ │ ├── search.py │ │ ├── calculator.py │ │ └── weather.py │ ├── models/ # LLM & embedding clients, model config │ │ ├── __init__.py │ │ ├── llm_client.py │ │ └── embeddings.py │ ├── prompts/ # system & task prompt templates │ │ ├── __init__.py │ │ ├── system_prompts.py │ │ └── agent_prompts.py │ ├── utils/ # logging, config loading, shared helpers │ │ ├── __init__.py │ │ ├── helpers.py │ │ ├── logger.py │ │ └── config.py │ └── api/ # expose the agent over HTTP (FastAPI/Flask) │ ├── __init__.py │ ├── routes.py │ └── schemas.py ├── tests/ # unit, integration, and per-tool tests │ ├── __init__.py │ ├── test_agent.py │ ├── test_tools.py │ └── test_api.py ├── data/ # sample data, knowledge base, eval sets │ ├── examples.json │ └── knowledge_base/ ├── logs/ # run logs for debugging & monitoring │ └── .gitkeep └── main.py # entry point: CLI, API server, or playground
The root holds project-wide files; the real code lives under src/, split into small, single-purpose packages. Here’s what each one holds — and where we covered the idea behind it:
| Package | What lives there | Where we covered it |
|---|---|---|
agent/ | The core reason → act → observe loop, the executor that runs tool calls, run state, memory, and planning. | Agentic AI — the loop & four building blocks |
tools/ | One module per capability — search, calculators, database and API calls, external actions — each with a description the model uses to choose it. | MCP & tool design |
models/ | LLM and embedding clients, provider settings, and model configuration. | Models & Local LLMs |
prompts/ | System instructions and prompt templates, kept out of application logic and versioned in one place. | Customizing & prompt engineering |
utils/ | Reusable helpers, configuration loaders, parsers, and logging. | the plumbing every project needs |
api/ | Routes and schemas that expose the agent over HTTP (FastAPI, Flask, …). | AI Pipelines — the request path |
tests/ | Unit, integration, per-tool, and failure-case tests — plus your eval cases. | Evals & reliability |
data/ & logs/ | Evaluation datasets, knowledge sources, and examples; run traces and debugging records. | Production & Operations |
This is the destination, not the starting line. Begin with main.py, one agent.py loop, and a single tool; add a package only when a file grows too big or a concern (prompts, models, an API) earns its own home. The two files worth creating immediately, though, are .gitignore and .env — so a secret never lands in your repo.
Start with the lightweight path. Sketch a skillset extension on paper: choose one real external action you’d want in your chat assistant (e.g. “look up the status of a Jira ticket”) and write the endpoint spec (name, inference description, URL, parameters) — the same shape works whether you later wire it into Copilot Chat or as an MCP server for Claude Code. Then, if you have Node, npm install @github/copilot-sdk, run the four-line snippet, and confirm you get a response. You’ve now touched both ends of the builder spectrum.
(1) You want a teammate to type @billing show overdue invoices in their assistant — which rung, and would you use a Copilot Extension, a Claude MCP server, or a ChatGPT/Gemini connector? (2) You’re building a customer-facing support chat box with custom logic — which rung, and name one agent SDK you could reach for? (3) What does an agent SDK (Copilot, Claude, or OpenAI) save you from building yourself? (4) What is BYOK and when would you use it?
Check your answers
- Rung ③, and any of the three works: An
@mention-invoked lookup exposing an external service to many users is rung ③ — a connector / Extension / MCP server. The rung is the decision; the product just follows your platform, so a Copilot Extension, a Claude MCP server, or a ChatGPT/Gemini connector are all correct choices, not competing ones. - Rung ④, an agent SDK: Building your own product with an embedded agent and custom logic is the top rung. You could reach for the GitHub Copilot SDK, the Claude Agent SDK, or the OpenAI Agents SDK.
- The whole agentic runtime: An agent SDK spares you from hand-building the planning loop, multi-turn context management, tool orchestration, model routing, MCP integration, and authentication, plus permissions, safety boundaries, and failure handling. It hands you a production-tested runtime as a library so you build your product on top instead of reinventing the platform first.
- Bring Your Own Key: BYOK is an auth option where, instead of using your existing Copilot subscription, you supply your own provider key — with providers like OpenAI, Azure AI Foundry, or Anthropic. Use it when you want to bill or route through your own provider account rather than the built-in subscription.