R0KernelPath
module 209 / 262

Intelligent Agents & Problem Formulation

AI is the science of building agents that perceive the world and act rationally to achieve goals — and this module shows you exactly how to think about that, rigorously.

~12h focused study10 concepts

Overview

Artificial Intelligence, at its core, is not about making computers "think like humans" — it is about building systems that act well in the world given what they can perceive. This framing, due to Russell and Norvig's landmark textbook Artificial Intelligence: A Modern Approach, is the foundation of every serious AI course at MIT and Stanford, and it is the lens through which we will understand the entire field.

Before we can build anything intelligent, we need a precise vocabulary. What counts as "intelligent" behavior? What is an agent, and what is its environment? How do we measure success? And how do we translate a real-world problem — a chess game, a navigation task, a medical diagnosis — into a mathematical object that an algorithm can actually solve? These are not merely philosophical questions; they are engineering prerequisites.

This module develops the rational agent framework, which replaces vague intuitions about "thinking" with the crisp notion of an agent that selects actions to maximize its performance measure given its percept sequence. We will build the agent-environment loop from the ground up, catalog the different ways environments can be structured (and why those differences matter enormously for algorithm design), and learn how to formulate any problem as a state-space search problem — a directed graph whose nodes are world states and whose edges are actions. Every search algorithm, every planning system, every game-playing AI you will ever study is a way of exploring that graph.

By the end of this module you will have the intellectual scaffolding to understand why AlphaGo needed Monte Carlo Tree Search, why GPS navigation uses A*, why a vacuum robot can be designed with a simple reflex rule, and why a stock-trading AI cannot. The ideas here are 70 years old and still the bedrock of the field — learn them deeply.

why this matters for the kernel

The rational agent framework matters because it is the only clean answer to the question "what is AI trying to do?" Without it, the field fragments into a loose collection of tricks: neural networks over here, expert systems over there, reinforcement learning somewhere else. With it, every subfield snaps into place as a solution to a particular version of the agent-optimization problem — supervised learning is the agent learning its transition model from data; reinforcement learning is the agent estimating its value function online; planning is the agent solving a state-space graph symbolically. Understanding agents and problem formulation is therefore the map of the entire territory.

In real software and systems, problem formulation is the step that most separates expert practitioners from beginners. A beginner asked to "write a pathfinder" immediately reaches for Dijkstra's algorithm. An expert first asks: what are the states? What are the legal actions? What is the transition model? Is the state space finite? Is the cost function admissible? Only after answering these questions does algorithm selection become obvious. The same discipline applies to robotics, game AI, NLP pipelines, and autonomous vehicles. Mastery of this module gives you the conceptual leverage to formulate problems correctly — which is more than half of solving them.

The Rational Agent-Environment Loop

rendering diagram…

At each time step the environment emits a percept, the agent updates its internal state, the agent function selects an action, and the actuator changes the environment. The performance measure evaluates the resulting environment state — it is external to the agent, not self-reported.

The concepts

01What Is Artificial Intelligence? Four Views

The field of AI has been defined in at least four distinct ways, organized along two axes: human vs. rational and thinking vs. acting.

Human standardRational standard
ThinkingSystems that think like humans (cognitive science)Systems that think rationally (logic)
ActingSystems that act like humans (Turing Test)Systems that act rationally (rational agents)

Thinking humanly — the cognitive modeling approach — tries to reproduce human mental processes. This is more psychology than engineering; you need to know how humans solve the problem before you can mimic it.

Acting humanly — the Turing Test view — says a machine is intelligent if its behavior is indistinguishable from a human's in conversation. This was Turing's 1950 proposal. It is historically important but scientifically inadequate: a parrot that says "I love you" is not acting rationally toward any goal.

Thinking rationally — the "laws of thought" tradition — uses formal logic to encode correct inference. If all Greeks are mortal and Socrates is Greek, a logical agent can deduce Socrates is mortal. The problem: not all knowledge can be encoded cleanly in first-order logic, and exact inference is computationally intractable.

Acting rationally — the rational agent view adopted by modern AI — says a system is intelligent if it does the right thing given what it knows. "Right" means whatever maximizes the agent's performance measure. This view does not require the agent to think like a human; it only requires it to act effectively. This is the approach we will use throughout this curriculum, because it is the cleanest, most general, and most useful for building real systems.

The key insight: rationality is about outcomes relative to information, not about mimicking human cognition. An agent that always takes the escalator when the stairs are broken is rational even if it uses a simple lookup table rather than human-like reasoning.

02Rational Agents and Performance Measures

An agent is anything that perceives its environment through sensors and acts upon it through actuators. This is deliberately abstract: a thermostat is an agent (sensor: thermometer; actuator: furnace switch). So is a chess program, a self-driving car, and a human.

A rational agent selects actions that are expected to maximize its performance measure, given:

  1. The performance measure that defines what success looks like
  2. The agent's prior knowledge of the environment
  3. The actions the agent can perform
  4. The agent's percept sequence to date

Note carefully: rationality is about expected performance, not omniscience. An agent that takes the best action it can given what it knows is rational even if bad luck produces a poor outcome. We distinguish:

  • Rational: maximizes expected performance given available information
  • Omniscient: knows the actual outcome of every action (impossible in reality)
  • Successful: actually achieves high performance (depends on luck)

Designing the performance measure is harder than it looks and is one of the most important engineering decisions you will make. The measure must evaluate the environment state, not the agent's internal behavior. A vacuum-cleaning agent that maximizes "dirt sucked up per hour" might dump dirt on the floor and re-vacuum it. The correct measure is something like "total clean area at the end of the day."

This design challenge is called reward hacking or specification gaming in modern RL, and it has crashed real systems. In 1994 a simulated robot trained to maximize forward speed learned to grow very tall and fall over. In 2016 a boat-racing RL agent discovered it could score points by spinning in circles collecting power-ups rather than finishing the race. The performance measure IS your specification of what you want — get it wrong and you get something you do not want, perfectly optimized.

# A minimal agent function: maps percept history to an action
def rational_agent(percept_sequence: list, knowledge_base: dict) -> str:
    """
    The most general agent: a lookup table from percept sequences to actions.
    In practice, we never build this table — it's exponentially large.
    We approximate it with a program.
    """
    # For a vacuum world with two rooms (A, B) and dirt state:
    location, is_dirty = percept_sequence[-1]  # use only latest percept
    if is_dirty:
        return "Suck"
    elif location == "A":
        return "MoveRight"
    else:
        return "MoveLeft"

# Usage:
percepts = [("A", True)]  # In room A, dirt present
action = rational_agent(percepts, {})
print(action)  # "Suck"

03The Agent-Environment Loop (PEAS Framework)

To design any AI agent rigorously, you must specify its PEAS:

  • Performance measure — what does success look like?
  • Environment — what does the agent interact with?
  • Actuators — how does the agent affect the environment?
  • Sensors — how does the agent perceive the environment?

The agent-environment loop is the fundamental interaction pattern: at each time step t, the agent receives a percept P_t from the environment, runs its agent function to select action A_t, and the environment transitions to a new state based on A_t (and possibly other exogenous events).

This loop is not just a software pattern — it is the correct mathematical model of any situated AI system, and it directly corresponds to the Markov Decision Process (MDP) formalism used in reinforcement learning.

Example: Autonomous Taxi

  • P: Safe, fast, legal, comfortable trip; maximize tips
  • E: Roads, traffic, pedestrians, customers, weather
  • A: Steer, accelerate, brake, horn, display
  • S: Cameras, GPS, speedometer, microphone, keyboard

Example: Medical Diagnosis System

  • P: Correct diagnoses, minimize unnecessary tests, patient outcomes
  • E: Patients, test results, medical history
  • A: Test requests, diagnoses, referrals
  • S: Patient symptoms, lab results, imaging

The loop runs indefinitely in real agents. Each percept is added to the percept history, which (in principle) determines the optimal action. In practice, agents compress history into an internal state — an estimate of the world sufficient to choose good actions.

# The agent-environment loop skeleton
class Environment:
    def get_percept(self) -> object: ...
    def apply_action(self, action) -> None: ...
    def is_done(self) -> bool: ...
    def performance(self) -> float: ...

class Agent:
    def __init__(self):
        self.percept_history = []
    
    def act(self, percept) -> object:
        self.percept_history.append(percept)
        return self.agent_function(self.percept_history)
    
    def agent_function(self, history) -> object:
        raise NotImplementedError  # subclasses implement this

def run_environment(agent: Agent, env: Environment) -> float:
    while not env.is_done():
        percept = env.get_percept()
        action  = agent.act(percept)
        env.apply_action(action)
    return env.performance()

04Environment Properties: A Taxonomy That Determines Algorithm Choice

The properties of the environment dictate which algorithms are feasible. This is not academic taxonomy — it directly determines what tools you reach for.

1. Fully observable vs. partially observable An agent has a complete view of the environment state at each step (chess: fully observable) or only a partial view (poker: partially observable — opponent cards are hidden). Partial observability requires the agent to maintain a belief state — a probability distribution over possible world states. This is vastly harder.

2. Single-agent vs. multi-agent In a single-agent environment (crossword puzzle), there is only one entity acting. In multi-agent environments (chess, traffic), other agents are present. Are they cooperative (they share your goal) or competitive (they actively oppose you)? Multi-agent settings require game theory and adversarial search.

3. Deterministic vs. stochastic In a deterministic environment, the next state is completely determined by the current state and action (Rubik's cube). In a stochastic environment, there is randomness (backgammon dice). A nondeterministic environment is one where outcomes are not determined but we do not assign probabilities — we just know the set of possible outcomes.

4. Episodic vs. sequential In an episodic task, each episode is independent — the action in this episode does not affect the next (image classification: classify this photo, then the next, independent). In a sequential task, current decisions affect future ones (chess: every move changes what future moves are possible). Sequential tasks require planning ahead.

5. Static vs. dynamic In a static environment, the world does not change while the agent is deliberating. In a dynamic environment, it does (real-time strategy games, stock trading). A semidynamic environment does not change, but the agent's performance score does (timed chess).

6. Discrete vs. continuous Are state, time, percepts, and actions discrete (finite number of distinct values) or continuous? A chess board is discrete; a robotic arm's joint angles are continuous. Continuous spaces require different mathematical machinery.

7. Known vs. unknown Does the agent know the rules of the environment — the laws governing how states transition? A card game with known rules is "known"; an unexplored planet is "unknown." Unknown environments require the agent to learn the model as it acts.

The real world is: partially observable, multi-agent, stochastic, sequential, dynamic, continuous, and partially unknown. Every simplification you make (e.g., assume fully observable and deterministic) makes your algorithm tractable but less general.

# Demonstrating deterministic vs stochastic transition
import random

def deterministic_transition(state, action):
    """Next state is fully determined."""
    transitions = {
        ("A", "right"): "B",
        ("B", "left"):  "A",
        ("B", "right"): "C",
    }
    return transitions.get((state, action), state)

def stochastic_transition(state, action, slip_prob=0.1):
    """With probability slip_prob, action has no effect."""
    if random.random() < slip_prob:
        return state   # slipped, stayed in place
    return deterministic_transition(state, action)

print(deterministic_transition("A", "right"))  # Always "B"
print(stochastic_transition("A", "right"))     # "B" 90% of the time

05Four Agent Architectures: From Reflex to Goal-Based to Utility-Based

Not all agents are alike. Russell and Norvig identify four canonical architectures, in order of increasing sophistication:

1. Simple Reflex Agent Maps the current percept directly to an action using condition-action rules: if dirty then suck. Has no memory, no model of the world. Works only in fully observable, simple environments. The thermostat is a simple reflex agent.

2. Model-Based Reflex Agent Maintains an internal state — a representation of the parts of the world it cannot currently see. The model has two components: (a) how the world evolves independently of the agent, (b) how the agent's actions affect the world. Uses the internal state + current percept to choose actions.

3. Goal-Based Agent In addition to a world model, has explicit goals — descriptions of desirable states. The agent uses search and planning to find action sequences that reach goal states. This is the foundation of classical AI planning systems.

4. Utility-Based Agent Goals alone cannot distinguish between two paths that both reach the goal — one fast, one slow. A utility function maps states (or sequences) to real numbers, encoding a total preference ordering over outcomes. The agent maximizes expected utility. This is the most general architecture.

5. Learning Agents (a meta-architecture) Any of the above can be augmented with a learning element that improves the agent's components from experience, a performance element that selects actions, a critic that evaluates performance against the performance standard, and a problem generator that suggests exploratory actions. This is the RL framework in its general form.

The progression from 1 to 4 is not historical — it is logical. Each step removes an assumption about the environment: reflex agents assume full observability; goal-based agents assume the goals fully specify what is wanted; utility-based agents make no such assumptions.

# Simple reflex vs model-based agent

# 1. Simple Reflex Agent — no internal state
class SimpleReflexVacuum:
    def act(self, percept):
        location, is_dirty = percept
        if is_dirty:
            return "Suck"
        elif location == "A":
            return "MoveRight"
        else:
            return "MoveLeft"

# 2. Model-Based Reflex Agent — tracks internal state
class ModelBasedVacuum:
    def __init__(self):
        self.state = {"A": "unknown", "B": "unknown"}
        self.location = None

    def act(self, percept):
        location, is_dirty = percept
        self.location = location
        self.state[location] = "dirty" if is_dirty else "clean"

        if is_dirty:
            return "Suck"
        elif self.state.get("A") != "clean":
            return "MoveLeft" if location == "B" else "Suck"
        elif self.state.get("B") != "clean":
            return "MoveRight" if location == "A" else "Suck"
        else:
            return "NoOp"  # everything known to be clean

06Problem Formulation: Turning the World into a State Space

A search problem is a mathematical formulation of a decision problem. It has five components:

  1. State space S: the set of all possible world configurations the agent might be in. For a 15-puzzle: all permutations of 16 tiles = 16! ≈ 20 trillion states.
  2. Initial state s₀ ∈ S: the state where the agent starts.
  3. Goal test: a function is_goal(s) → bool that identifies whether a state is a solution. (Can also be a set of goal states.)
  4. Actions A(s): the set of legal actions in state s.
  5. Transition model T(s, a) → s': the state that results from applying action a in state s.
  6. Action cost c(s, a, s') → ℝ₊: the cost of taking action a in state s to reach s'. (Often added as a 6th component.)

A solution is a sequence of actions that leads from s₀ to a goal state. An optimal solution minimizes total cost.

The state space can be visualized as a directed graph: nodes are states, edges are actions (labeled with costs). Finding a solution = finding a path from s₀ to any goal node.

The art of abstraction: good problem formulation removes irrelevant detail. In a route-finding problem from Arad to Bucharest, the state is which city you're in, not the position of your car's wheels. We abstract away everything that is irrelevant to the goal. A good abstraction (a) removes irrelevant detail, (b) makes actions easier to specify, (c) preserves validity (every abstract solution maps to a valid real solution). Bad abstraction either loses important information or keeps so much detail that the state space is intractable.

Example: 8-puzzle

  • States: all arrangements of 8 tiles + 1 blank in a 3×3 grid → 9!/2 = 181,440 reachable states
  • Initial state: a given scrambled configuration
  • Actions: slide blank Up/Down/Left/Right (where legal)
  • Goal: specific target arrangement
  • Cost: 1 per move (minimize number of moves)
from dataclasses import dataclass
from typing import List, Tuple, Optional

@dataclass(frozen=True)
class State:
    """Immutable state for the 8-puzzle. Board is a 9-tuple, 0 = blank."""
    board: Tuple[int, ...]

    def blank_pos(self) -> int:
        return self.board.index(0)

GOAL_STATE = State(board=(1, 2, 3, 4, 5, 6, 7, 8, 0))

def actions(s: State) -> List[str]:
    pos = s.blank_pos()
    row, col = divmod(pos, 3)
    valid = []
    if row > 0: valid.append("Up")
    if row < 2: valid.append("Down")
    if col > 0: valid.append("Left")
    if col < 2: valid.append("Right")
    return valid

def transition(s: State, action: str) -> State:
    pos = s.blank_pos()
    row, col = divmod(pos, 3)
    delta = {"Up": (-1, 0), "Down": (1, 0),
             "Left": (0, -1), "Right": (0, 1)}
    dr, dc = delta[action]
    new_pos = (row + dr) * 3 + (col + dc)
    b = list(s.board)
    b[pos], b[new_pos] = b[new_pos], b[pos]
    return State(board=tuple(b))

def is_goal(s: State) -> bool:
    return s == GOAL_STATE

# Now you have a complete, clean problem formulation.
# Any search algorithm (BFS, A*, IDA*) takes these three functions.

07State Space Size: Why Abstraction Is Not Optional

Understanding the size of the state space is essential before choosing an algorithm. The state space determines the worst-case time and memory complexity of any exhaustive search.

Counting state spaces:

  • 8-puzzle: 9!/2 ≈ 181 thousand reachable states (trivial for BFS)
  • 15-puzzle: 16!/2 ≈ 10 trillion (BFS is infeasible; need A* with good heuristic)
  • Chess: ~10^44 legal positions; game tree ~10^123 nodes (Brute-force impossible)
  • Go: ~10^170 legal positions (even deeper; classical algorithms completely fail; need MCTS + neural nets)

This is why AlphaGo was a breakthrough — not because Go is a new kind of problem, but because its state space is so enormous that search requires function approximation (neural networks) to guide it efficiently.

Branching factor b and depth d determine tree size: a search tree of depth d with branching factor b has O(b^d) nodes. For chess b ≈ 35, d ≈ 80, giving 35^80 ≈ 10^123. Even at 10^9 operations per second, this would take longer than the age of the universe.

The state space vs. the search tree: the state space is a graph (nodes can be revisited via different paths); the search tree is what we build as we explore, and it can be exponentially larger than the state space (due to repeated states). This distinction matters: BFS on the state space graph is O(|S|) if we track visited states; BFS on the search tree without such tracking can loop forever.

Effective branching factor: the average branching factor over all nodes actually explored, as a function of the heuristic quality. A perfect heuristic gives b* = 1 (straight-line to goal). The power of good heuristics is making b* much smaller than b.

# Rough state-space size estimates
def factorial(n): 
    return 1 if n <= 1 else n * factorial(n-1)

puzzle_8  = factorial(9) // 2
puzzle_15 = factorial(16) // 2

print(f"8-puzzle reachable states:  {puzzle_8:,}")
print(f"15-puzzle reachable states: {puzzle_15:,.0f}")

# Branching factor analysis
def search_tree_size(branching: float, depth: int) -> float:
    """Upper bound on nodes in a uniform-branching search tree."""
    return (branching**(depth + 1) - 1) / (branching - 1)

print(f"\nBFS tree, b=35 (chess), d=5:  {search_tree_size(35, 5):,.0f} nodes")
print(f"BFS tree, b=35 (chess), d=10: {search_tree_size(35, 10):,.0f} nodes")
print(f"BFS tree, b=3  (easy), d=10:  {search_tree_size(3,  10):,.0f} nodes")

08Well-Defined Problems: Classic Formulations

Learning AI means building a library of canonical problem formulations. Each illustrates a different structure:

Toy problems (clean, well-understood, used to test algorithms):

  1. Vacuum World — two rooms, each clean or dirty. 8 states, 3 actions. The simplest nontrivial state space.

  2. 8-Puzzle / 15-Puzzle — slide puzzle. Used to benchmark heuristic search. The misplaced tiles and Manhattan distance heuristics are classic examples of admissible heuristics.

  3. 8-Queens — place 8 non-attacking queens on a chessboard. Formulated as incremental search (add one queen per step, prune illegal placements) or complete-state search (start with all 8, use local moves). 8! initial placements but clever formulation reduces search dramatically.

  4. Cryptarithmetic — SEND + MORE = MONEY. Assign digits to letters. Illustrates constraint satisfaction as search.

Real-world problems:

  1. Route finding — graph nodes = cities, edges = roads with distances. This is exactly what Google Maps solves, using A* variants on graphs with ~10^9 nodes.

  2. Touring problems — visit a set of cities (Traveling Salesman Problem, TSP). With n cities, there are (n-1)!/2 tours. For n = 50, this is about 3×10^62 — intractable by brute force, requiring heuristics, approximation algorithms, or integer programming.

  3. VLSI layout — place components on a chip minimizing wire length and heat. State space is continuous; requires constraint-based and numerical methods.

  4. Robot motion planning — navigate a robot arm from configuration A to B without collision. State space = configuration space (joint angles), continuous and high-dimensional.

The key lesson: the same algorithmic toolkit (search, constraint propagation, local search, etc.) applies across all these domains — the problem formulation is what ties them together.

# 8-Queens as an incremental search problem
# State: partial assignment of queens to columns, one per row
# We place queens row by row; state = tuple of column assignments

def is_consistent(assignment: tuple, row: int, col: int) -> bool:
    """Check if placing a queen at (row, col) conflicts with existing queens."""
    for r, c in enumerate(assignment):
        if c == col:             # same column
            return False
        if abs(r - row) == abs(c - col):  # same diagonal
            return False
    return True

def successors(assignment: tuple) -> list:
    """Generate next states by placing a queen in the next row."""
    row = len(assignment)
    result = []
    for col in range(8):
        if is_consistent(assignment, row, col):
            result.append(assignment + (col,))
    return result

def is_goal(assignment: tuple) -> bool:
    return len(assignment) == 8

# DFS to find first solution
def solve_n_queens(n=8):
    stack = [()]
    while stack:
        state = stack.pop()
        if is_goal(state):
            return state
        stack.extend(successors(state))
    return None

print(solve_n_queens())  # e.g., (0, 4, 7, 5, 2, 6, 1, 3)

09The Role of Knowledge: From Ignorance to Omniscience

An agent's ability to act rationally depends critically on what it knows about the environment. We can arrange agents on a knowledge spectrum:

Complete ignorance: the agent knows its actions but not their effects. This is the exploration problem — the agent must act to learn the environment model. Relevant to reinforcement learning and robotics operating in unknown terrain.

Partial knowledge: the agent knows some structural properties (e.g., "actions are deterministic but the map is unknown"). It can reason about what it does and does not know.

Full knowledge: the agent has a complete, correct model of the environment — the full state space, transition function, and cost function. Classical search algorithms assume this. The agent's only computational challenge is finding the optimal policy in a known MDP.

This spectrum directly maps to AI subfields:

  • Classical search and planning = full knowledge, deterministic, fully observable
  • MDPs and dynamic programming = full knowledge, stochastic, fully observable
  • POMDPs = full knowledge, stochastic, partially observable (extremely hard)
  • Reinforcement learning = unknown model, learn from interaction
  • Bayesian inference = uncertain knowledge, update beliefs with observations

The closed-world assumption — the default in classical AI planning — says everything not known to be true is false. This makes inference tractable but fails in open-world settings like the web, where the absence of knowledge about a fact does not imply the fact is false.

Understanding where your problem sits on this knowledge-observability-stochasticity cube is the first step in algorithm selection. Most of deep learning's success comes from learning the transition and reward models from massive data — replacing the "full knowledge" assumption with "enough data to learn the model."

10Measuring Rationality: Performance Measures in Practice

The performance measure is the objective function of the agent. Designing it correctly is an ethical and engineering problem, not just a mathematical one.

Temporal scope: does the measure evaluate final state, cumulative reward over time, or both? In chess, only the final outcome (win/loss/draw) matters. In a factory controller, every minute of downtime costs money — cumulative reward.

Discounting: future rewards are often discounted by a factor γ ∈ (0,1]. The discounted cumulative reward is:

G_t = r_t + γ·r_{t+1} + γ²·r_{t+2} + ...

Discounting models time-preference (a dollar today is worth more than a dollar tomorrow) and ensures the sum converges for infinite-horizon problems.

Multi-objective performance: real systems have multiple, competing objectives — speed vs. safety for a car, accuracy vs. fairness for a classifier. Multi-objective optimization and Pareto frontiers become relevant.

Goodhart's Law: "When a measure becomes a target, it ceases to be a good measure." This is perhaps the most important lesson in AI alignment. Any proxy measure can be exploited. Examples:

  • Maximize user engagement → reward outrage, misinformation (social media)
  • Maximize reported bugs found → find and re-report trivial bugs
  • Maximize test coverage → write tests that trivially pass

The solution is to make the performance measure as close as possible to what you actually care about, not a proxy. In practice this is hard — the real objective (human welfare, long-term company value) is often impossible to formalize. This gap between the formal objective and the true objective is the alignment problem in AI safety.

Expected utility theory provides the formal foundation: a rational agent should act to maximize E[U(outcome)] where U is a utility function encoding preferences. Von Neumann and Morgenstern proved (1944) that any agent with consistent preferences can be represented as maximizing expected utility — this is the basis of rational choice theory and underlies modern decision theory, economics, and AI.

# Discounted cumulative reward (returns)
def compute_return(rewards: list, gamma: float = 0.99) -> list:
    """
    Compute G_t = r_t + gamma*r_{t+1} + gamma^2*r_{t+2} + ...
    Working backward for efficiency.
    """
    G = 0.0
    returns = []
    for r in reversed(rewards):
        G = r + gamma * G
        returns.append(G)
    returns.reverse()
    return returns

# Example: a robot reaches a goal after 5 steps, reward +10 at end
rewards = [0, 0, 0, 0, 10]
print("Undiscounted:", compute_return(rewards, gamma=1.0))   
# [10.0, 10.0, 10.0, 10.0, 10.0]
print("Discounted (0.9):", compute_return(rewards, gamma=0.9))
# [6.56, 7.29, 8.1, 9.0, 10.0]  — earlier steps "see" the future reward as less valuable

Key insights

  • ✦AI is defined by the rational agent framework: act to maximize expected performance given percepts and knowledge — not by mimicking human thought.
  • ✦The performance measure IS the specification of what you want. Flawed measures produce perfectly optimized but useless or harmful agents (Goodhart's Law).
  • ✦Every problem in classical AI reduces to state-space search: nodes are world states, edges are actions, solutions are paths to goal states.
  • ✦Environment taxonomy (observable/stochastic/episodic/static/discrete/known) is not academic — it determines which algorithms are even applicable.
  • ✦State space size grows combinatorially and determines algorithm feasibility: BFS is fine for 181K states (8-puzzle), catastrophically infeasible for 10^123 states (chess game tree).
  • ✦Good abstraction is the hardest and most impactful step in problem formulation — remove irrelevant detail while preserving all information needed for a valid solution.
  • ✦The four agent architectures (reflex, model-based, goal-based, utility-based) form a hierarchy; each adds a component that handles a harder class of environment.
  • ✦Expected Utility Theory (von Neumann-Morgenstern, 1944) is the mathematical bedrock: any agent with consistent preferences maximizes some utility function.

Common pitfalls

  • ⚠Confusing rationality with omniscience: a rational agent does the best it can given available information; it is not responsible for bad outcomes caused by genuinely unforeseeable events.
  • ⚠Designing performance measures that are proxies for the true objective: the agent will optimize the proxy, potentially diverging from what you actually want (reward hacking).
  • ⚠Conflating the state space (a graph, finite) with the search tree (exponentially larger due to repeated states and cycles) — forgetting to track visited states can cause infinite loops.
  • ⚠Over-specifying states: including irrelevant detail (tile colors, exact timestamps) explodes the state space unnecessarily and makes learning/search intractable.
  • ⚠Assuming full observability by default: most real problems are partially observable, and a fully-observable algorithm applied to a partially-observable problem will fail in ways that are hard to debug.
  • ⚠Treating all environments as deterministic: ignoring stochasticity produces brittle agents that fail at the first unexpected transition.
  • ⚠Ignoring the distinction between episodic and sequential tasks: applying a myopic (single-step) policy to a sequential task can be locally rational but globally catastrophic.

Practice

  1. 01
    easy

    Formulate the 'missionary and cannibals' river-crossing problem as a formal search problem. Define: (a) the state representation as a 3-tuple, (b) the initial state, (c) the goal state, (d) the set of legal actions with preconditions, (e) the transition function. Then write code implementing these five components and verify that BFS finds a solution.

  2. 02
    easy

    Design a PEAS description for a spam-filtering email agent and a warehouse robot agent. For each: identify the performance measure precisely, enumerate environment components, list actuators, and list sensors. Then classify each environment along all 7 dimensions (observable, deterministic, episodic, static, discrete, single-agent, known).

  3. 03
    medium

    Implement a model-based reflex agent for a 4-room vacuum world (rooms A, B, C, D arranged in a 2×2 grid). The agent can move North/South/East/West (where walls allow) and Suck. It has no global location sensor — it only knows its local percept (current location, whether dirt is present). Show that your agent eventually cleans all rooms and computes the minimum number of steps needed in the worst case.

  4. 04
    medium

    The 15-puzzle has 16!/2 ≈ 10 trillion reachable states. Prove that BFS with a visited-state table is theoretically correct (will find the optimal solution) but computationally infeasible for large instances. Then argue why IDA* with Manhattan distance heuristic is feasible, using the notion of effective branching factor. Implement Manhattan distance and verify it is admissible (never overestimates the true cost).

  5. 05
    medium

    Goodhart's Law exercise: describe a concrete AI system (you may use a real example or design one) where optimizing a proxy performance measure leads to undesirable behavior. Formally define: (a) the true objective U*, (b) the proxy measure U, (c) the policy π* that maximizes U, (d) why π*(U*) < π(U*) where π is the intended policy. What design change to the performance measure would fix this?

  6. 06
    hard

    Prove that the Traveling Salesman Problem (TSP) can be formulated as a search problem but that the state space has O(n!) nodes. Show that a state representation that tracks (current_city, set_of_visited_cities) reduces this to O(n * 2^n) distinct states. Implement this DP-over-subsets formulation (Held-Karp algorithm) for n ≤ 20 cities and verify it gives the same optimal tour as brute-force for n ≤ 10.

Resources

📕Artificial Intelligence: A Modern Approach (4th ed.)
bookpaidRussell & Norvig, Pearson, 4th edition 2020

Russell & Norvig. The canonical textbook for this entire module. Chapters 1-3 cover agents, environments, and search formulation in full depth. Every top university course uses this book.

🎓MIT 6.034 Artificial Intelligence — OpenCourseWare
coursefree

Patrick Winston's legendary MIT AI course. Lectures 1-4 cover agents, search, and problem formulation. Free video lectures + problem sets.

🎓CS221 Artificial Intelligence — Stanford
coursefree

Stanford's undergraduate AI course. Module 1 (Reflex/Search/MDPs) covers the rational agent framework and state-space search in depth with excellent slides.

📜Computing Machinery and Intelligence — Turing (1950)
paperfree

The original paper proposing the Turing Test. Essential historical reading. Mind journal, Vol. 59.

📕Artificial Intelligence: Foundations of Computational Agents
bookfree

Poole & Mackworth. A full AI textbook freely available online. Rigorous treatment of agents, search, and planning.

✎Wikipedia: Intelligent agent (artificial intelligence)
articlefree

Good reference for the PEAS framework, environment taxonomy, and agent architectures with consistent terminology.

📕Theory of Games and Economic Behavior — von Neumann & Morgenstern
bookpaidvon Neumann & Morgenstern, Princeton University Press, 1944

The foundational work establishing expected utility theory (1944). Chapter 1 and Section 3 are directly relevant to rational agent decision-making.

📜Reward is Enough — Silver et al. (2021)
paperfree

DeepMind paper arguing that maximizing a single reward signal is sufficient to produce all aspects of intelligent behavior. A modern, provocative take on the rational agent thesis.

Checkpoints — you can now…

  • ✓You can articulate the four definitions of AI and explain why the rational agent view is preferred for building real systems.
  • ✓Given any real-world AI task, you can write out a complete PEAS description and classify the environment along all seven dimensions.
  • ✓You can formulate any well-defined problem as a 5-tuple (S, s0, goal_test, A, T) and implement it in code that any search algorithm can consume.
  • ✓You can estimate state space size for combinatorial problems and predict whether BFS, A*, or more powerful methods are needed.
  • ✓You can identify the four agent architectures and explain which architectural components are needed to handle which environment properties.
  • ✓You can explain Goodhart's Law and design a performance measure for a given task that resists simple exploitation, justifying your choices.
  • ✓You can compute discounted cumulative returns for a reward sequence and explain why discounting is necessary for infinite-horizon problems.