Is ChatGPT an agent? Today we start from the 1995 definition and check how today's tools measure up against it.
I am a software engineer of ten years, currently at ZEP. I finished my master's at Seoul National University of Science and Technology, where this course is taught, researching data pipelines and distributed systems, with papers at BIGCOMP and in Frontiers in Big Data.
At ZEP I run a WebRTC cluster serving 4M MAU. I moved it from LiveKit Cloud to self-run OCI infrastructure, cut infrastructure cost by 60%, and presented the migration at the Oracle Digital Native Roundtable.
I build Ouroboros, an open-source Agent OS: a framework that pins requirements down into a Seed through interviews and verifies execution results with deterministic gates. ZEP QUIZ (over 600K quizzes a month) runs an agent that improves its own tools. I presented this architecture at the ICML 2026 AI×Education workshop.
| Part | Topic |
|---|---|
| A1 | History of the agent definition |
| A2 | Forty-year lineage: Contract Net to A2A |
| A3 | Anatomy of an LLM agent |
| DISC | The Bitter Lesson |
| LAB | First agent, repo fork and first PR |
Today the word ‘agent’ gets attached to chatbots, automation scripts, and background daemons alike. Since we use one word for different things, we need a criterion for deciding which systems to call agents. The starting point is the 1995 paper by Michael Wooldridge and Nicholas Jennings, “Intelligent Agents: Theory and Practice”. The paper sorted the loosely used term into two clear notions. The distinction still holds thirty years later.
The first the authors call the weak notion: to be called an agent, a system must have at least four properties.
An agent in the weak notion satisfies four properties.
M. Wooldridge & N. Jennings, “Intelligent Agents: Theory and Practice” (1995)
Autonomy means the system operates without direct intervention from humans or others, and has some control over its actions and internal state. Something that moves only when a person presses a button at every step is a tool, not an agent. Reactivity is the ability to perceive the environment and respond to changes in a timely way. The environment keeps changing, so a system that only replays a prearranged sequence is not reactive. Pro-activeness points the other way: not merely responding to stimuli, but setting goals and taking the initiative toward them. Reactivity alone stays at passive reflex; pro-activeness alone misses changes in the environment. Finally, social ability is the capacity to interact with other agents or humans through a communication language.
The second is the strong notion. Used mainly by AI researchers, it grants the system mental attitudes on top of the four properties, attitudes we would normally reserve for humans. The best known is the BDI model: belief, desire, intention. The agent holds beliefs about the world, has desires it wants realized, and commits to the intentions it actually pursues. Knowledge, and sometimes emotion, get added on. The strong notion treats the agent as an intentional system: an entity whose behavior is usefully explained as ‘it does Y because it believes X’.
Wooldridge and Jennings stressed using this intentional vocabulary as a tool for design and analysis. Instead of tracing a complex system's behavior at the physical or code level, describing it with ‘belief’ and ‘intention’ makes prediction and explanation far more compact. When we write a prompt like “you judged the user to be in a hurry, so take the fastest route”, we are designing a system in exactly that vocabulary. Thirty years ago the vocabulary had to be forced into code; now the model already understands it.
To draw the boundaries between three concepts, let us extend the contrast the authors gave. A program takes input and produces output by a fixed procedure. Neither autonomy nor reactivity is required. Call the function and it returns a value; leave it alone and it does nothing. An object goes one step further: it encapsulates state and behavior and exchanges messages, but it cannot decide for itself whether to comply. When another object calls a public method, the object must perform the request. Control lies with the caller. An agent, by contrast, receives a request and judges against its own goals whether to accept or refuse it. The authors' own words sum up the difference.
Ask of any system, “does it do this because it was requested, or because it judged the request to fit its own goals”, and you can see whether it belongs with objects or with agents. The code we build in today's lab sits on this boundary. A single while loop turns it from an object into an agent.
The history of agent communication spans roughly forty years. The 1980s and 1990s poured out ambitious protocols. The 2000s went quiet even as standardization advanced. From 2022 on, the same problems reappeared under different names alongside large language models.
The starting point is 1980 and Reid G. Smith's Contract Net Protocol, an answer to the question of how to divide work among scattered processing nodes in distributed problem solving. A manager node makes a task announcement, capable nodes bid, and the manager awards the contract. It brought the market metaphor into computational resource allocation. Manager and contractor roles switch dynamically per task. We reproduce this protocol with LLM agents in the week 3 lab.
The 1990s ripened attempts to standardize the form of communication. KQML (Knowledge Query and Manipulation Language) and its successor FIPA-ACL (Agent Communication Language) are the result. Drawing on speech act theory from the philosophy of language, they attached performatives such as ‘inform’, ‘request’, and ‘propose’ to messages. The attempt failed; why it failed, and which parts LLMs resolve, is week 4's topic.
The 2000s were, paradoxically, quiet. Standards documents grew precise, but no widely used agent ecosystem arrived. The protocols existed; agents smart enough to fill them did not.
The turning point is 2022 and ReAct, proposed by Yao et al. It wove reasoning and acting together inside a language model: think, use a tool, observe the result, think again. Then in 2024 MCP (Model Context Protocol) standardized how models and agents connect to tools and context. In 2025, A2A (Agent2Agent) took on discovery and task delegation between autonomous agents, and ACP (Agent Client Protocol) set out to standardize sessions, prompts, tool calls, diffs, and permission interactions between code editors and coding agents.
A2A's agent card revives the discovery problem FIPA tried to solve, and ACP reduces the N×M problem of integrating every coding agent into every IDE to a single common client-agent boundary.
To answer “is ChatGPT an agent?” we first need to see what an LLM agent is actually made of.
An LLM agent has three parts. First, the model: a function that takes text and returns the next text, with no state, no memory, and no effect on the outside world by itself. Second, the context: everything handed to the model on each call, that is, the system prompt, the conversation so far, observed results, and the list of available tools with their descriptions. The model sees only what is in the context. Third, the tool loop: when the model outputs “call this tool with these arguments”, an outside program actually runs the tool, appends the result to the context, and calls the model again. The call, run, feed-back cycle repeats until it stops.
LLM agent = model + context + tool loop.
This course's working definition. The four W&J properties, implemented with these three parts
Autonomy comes from the loop, not the model. A single model call is just a program: input in, output out. But wrap it in a while loop that runs the tools and feeds results back, and the system starts choosing its next action, reacting to observations, and running until it reaches the goal. You will write this loop yourself in the lab, in twenty lines.
Now grade the LLM agent against the four W&J properties. Autonomy: while the loop runs, no human intervenes at each step. Starting, stopping, and approving risky actions, though, often stay with a person. Reactivity: tool results feed back into the context, and the model changes its next move accordingly. Pro-activeness: given a goal in the prompt, the model plans the intermediate steps on its own. This is what ReAct provided. It decomposes a given goal; it does not set new ones. Social ability: a standard by which LLM agents reliably discover, delegate to, and verify each other does not yet exist.
| W&J 1995 property | LLM agent | Verdict |
|---|---|---|
| Autonomy | No intervention while the loop runs. Start, stop, approval stay human | Partial |
| Reactivity | Tool results feed back into context and change the next move | Met |
| Pro-activeness | Decomposes a given goal into steps on its own (ReAct) | Mostly met |
| Social ability | No standard for discovery, delegation, verification between agents | Not met (this course's subject) |
The basic form of ChatGPT, responding only to what the user types in a chat window, is reactive but, strictly speaking, neither autonomous nor pro-active. It waits for human input every turn, so the human owns the loop. It is a dialogue system, and falls short of the name agent. But wrap the same model in tools and a while loop, so that it updates its own context until it reaches the goal, and it largely qualifies as an agent in the weak notion.
In his 2019 essay “The Bitter Lesson”, Richard Sutton argued that across seventy years of AI, methods that hand-coded human knowledge lost, again and again, to general methods backed by computation and data at scale. Search and learning, riding scale, win in the end. The claim collides head-on with this course's premise. If scale eventually solves everything, is careful research on protocols, contracts, and verification a waste of time?
We confirm in practice that autonomy comes from the loop, not the model. Hand the model two tools, a calculator and a file reader, and build a while loop in which the model picks its own tools, observes the results, and runs until it reaches the answer. You need Python 3.10 or later, the anthropic or openai package, and one API key. The example below uses only the standard library and the anthropic package.
import os, ast, operator, anthropic # ---- Tool 1: calculator (safe, no eval) ---- _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg} def _ev(node): if isinstance(node, ast.Constant): return node.value if isinstance(node, ast.BinOp): return _OPS[type(node.op)](_ev(node.left), _ev(node.right)) if isinstance(node, ast.UnaryOp): return _OPS[type(node.op)](_ev(node.operand)) raise ValueError("disallowed expression") def calculator(expression: str) -> str: """Evaluate an arithmetic expression string, e.g. '3 * (4 + 5)'.""" return str(_ev(ast.parse(expression, mode="eval").body)) # ---- Tool 2: file reader (blocks paths outside the working directory) ---- def read_file(path: str) -> str: """Return the contents of a text file.""" full = os.path.abspath(path) if not full.startswith(os.getcwd()): return "refused: path outside working directory" with open(full, encoding="utf-8") as f: return f.read()[:4000] TOOLS_IMPL = {"calculator": calculator, "read_file": read_file} # ---- Tool schemas handed to the model (description = interface) ---- TOOLS = [ {"name": "calculator", "description": "Evaluate an arithmetic expression.", "input_schema": {"type": "object", "properties": {"expression": {"type": "string"}}, "required": ["expression"]}}, {"name": "read_file", "description": "Read a text file in the working directory.", "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}, ] def run(goal: str, max_steps: int = 8): client = anthropic.Anthropic() # uses ANTHROPIC_API_KEY messages = [{"role": "user", "content": goal}] for step in range(max_steps): # ← this loop makes the agent resp = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, tools=TOOLS, messages=messages) messages.append({"role": "assistant", "content": resp.content}) if resp.stop_reason != "tool_use": # final answer → stop return "".join(b.text for b in resp.content if b.type == "text") results = [] for block in resp.content: # run tool calls → observe if block.type == "tool_use": out = TOOLS_IMPL[block.name](**block.input) print(f" [tool] {block.name}({block.input}) → {out}") results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(out)}) messages.append({"role": "user", "content": results}) return "stopped: max steps exceeded" # stop condition = guardrail if __name__ == "__main__": print(run("Read notes.txt and add up the numbers in it."))
The heart of this code is the single line for step in range(max_steps). Delete the loop and call messages.create once, and the system is a program again. Only the model's text saying it would call a tool remains; no tool actually runs. Put a few numbers in notes.txt and run it. The model calls read_file first, then, after seeing the result, calculator. Both calls print to the console.
Every assignment in this course is submitted as a PR from your fork of the course repository. Deadlines are judged by the time the PR was opened. In this lab you fork and open your first PR.
AGENTS.md hold all the rules. If you use a coding agent, have it read AGENTS.md first.roster/<student-id>.md with your GitHub username.submissions/<student-id>/ check compares against the username in this file.first_agent.py chains read_file → calculator on its own and returns the right answer.Submission rules are the same every week.
logs/.LLMs and agent tools are welcome on every assignment. What you asked for and what you threw away must remain in the commit history and logs.
Copyright in assignment code and projects stays with the student who made it.
Deliverable: add one more tool to the agent from the lab and solve a real task with it. Any tool works: fetch to pull text from the web, clock to tell the current time, write_note to save a memo to a file. Observe how the model's tool choice changes when the toolbox grows by one.
Format: commit to submissions/<student-id>/week-01/ in your fork and open a PR to upstream. (1) A working first_agent.py (three tools), (2) one paragraph on why you described the new tool the way you did (TOOLS.md), (3) the agent's run logs (logs/).
Grading: half is whether the code actually runs (reproducibility). Someone else must get the same result from your code and settings alone. State every setting except the API key: model name, tool schemas, how to run. The other half is whether what you tried and what you discarded shows honestly in the commit history and logs.
Same model, different results: why? Next week starts from this question. The difference comes from the design of the loop, the ‘harness’. Read ReAct (Yao et al. 2022) and learn context management, tool granularity, stop conditions, error recovery, and human intervention points. In the lab you attach two harness variants to the same task and measure the difference A/B.