AI Agent Beginner Tutorial: 03 - Basic Structure and Project Setup

Connect one live model call from an empty project and define Goal, State, Action, Observation, Tool, Runtime, and Stop Policy with data classes. An offline model keeps the structure independently testable before the loop is added.

Contents11 sections

Create a repository that is easy to read

The research-brief agent now has defined inputs, outputs, a tool allowlist, and explicit prohibitions. Map that boundary into one brief-agent repository, beginning with the model adapter and domain types.

TEXT
brief-agent/
├── src/brief_agent/
│   ├── config.py
│   ├── model.py
│   ├── domain.py
│   ├── runtime.py
│   └── cli.py
├── tests/
├── fixtures/
├── artifacts/
└── pyproject.toml

Configuration, model adapter, and command-line entry point stay separate. When tools and state arrive later, the entry point will not become a container for every behavior.

Read configuration from the environment once

PYTHON
import os
from dataclasses import dataclass

@dataclass(frozen=True)
class Settings:
    model: str
    offline: bool

def load_settings() -> Settings:
    return Settings(
        model=os.getenv("OPENAI_MODEL", ""),
        offline=os.getenv("BRIEF_AGENT_OFFLINE", "0") == "1",
    )

The SDK reads the API key from its standard environment variable. The key never enters Settings string output or logs.

Define a minimal model protocol

PYTHON
from typing import Protocol

class TextModel(Protocol):
    def complete(self, prompt: str) -> str: ...

class EchoModel:
    def complete(self, prompt: str) -> str:
        return f"OFFLINE: {prompt}"

EchoModel has no intelligence. It keeps the CLI, configuration, and tests runnable without a network.

Connect the live Responses API

PYTHON
from openai import OpenAI

class OpenAITextModel:
    def __init__(self, model: str) -> None:
        self.client = OpenAI()
        self.model = model

    def complete(self, prompt: str) -> str:
        response = self.client.responses.create(
            model=self.model,
            input=prompt,
        )
        return response.output_text
TEXT
CLI ──▶ TextModel.complete ──▶ OpenAI Responses API
 │
 └────▶ EchoModel (offline)

The CLI selects an adapter from Settings, asks “explain an agent loop,” and prints the result. The program is still one question and one answer.

Model-adapter acceptance

  • Offline mode makes no network request.
  • Online mode reports a clear error before requesting when no model is configured.
  • Logs contain no API key or complete private prompt.
  • The CLI depends only on the TextModel protocol.

With the model adapter connected, the next section defines the main agent structure and turns goals, state, actions, observations, tools, and stopping conditions into Python types.

Draw the main structure before writing the loop

TEXT
┌──────── Goal ────────┐
│ Topic, audience, tests│
└──────────┬───────────┘
           ▼
┌──────── Runtime ───────────────────────────┐
│ State ─▶ Model ─▶ Action ─▶ Tool ─▶ Observation │
│   ▲                                      │ │
│   └──────────────────────────────────────┘ │
│            Stop Policy / Budget             │
└────────────────────────────────────────────┘

The goal describes the task. State stores progress. The model chooses an action from state. A tool executes an action and returns an observation. The runtime connects every component. A stop policy decides success, failure, or budget exhaustion.

Fix the agent language with types

PYTHON
from dataclasses import dataclass, field
from typing import Any, Literal

@dataclass(frozen=True)
class Goal:
    topic: str
    audience: Literal["engineer", "manager"]

@dataclass(frozen=True)
class Action:
    kind: Literal["tool", "finish"]
    name: str = ""
    arguments: dict[str, Any] = field(default_factory=dict)
    answer: str = ""

@dataclass(frozen=True)
class Observation:
    ok: bool
    source: str
    data: dict[str, Any]

@dataclass
class AgentState:
    goal: Goal
    turn: int = 0
    observations: list[Observation] = field(default_factory=list)
    status: str = "running"

These types have no OpenAI SDK dependency. A model adapter converts current state into a request and converts model output into an action.

Give control to the runtime

PYTHON
class DecisionModel:
    def decide(self, state: AgentState) -> Action:
        raise NotImplementedError

class ToolRunner:
    def execute(self, action: Action) -> Observation:
        raise NotImplementedError

class StopPolicy:
    def check(self, state: AgentState) -> str | None:
        if state.turn >= 6:
            return "budget_exhausted"
        return None

The model cannot mutate state or call a tool directly. The runtime receives an action, checks allowlists and budgets, executes it, and appends the observation to state.

Follow one runtime turn

TEXT
state.turn += 1
      │
      ▼
model.decide(state)
      │
      ├── finish ─▶ validate answer ─▶ succeeded
      │
      └── tool ───▶ runner.execute ─▶ observation ─▶ state

Actions use structured fields instead of natural-language conventions such as “please call a tool.” Observations store both success and error so the model can see a failed tool on the next turn.

Leave advanced components out of the simple agent

Version one has no long-term memory, planner, multiple agents, MCP, or vector database. It needs only one goal, state, decision model, tool runner, and loop.

Omitting those capabilities preserves the minimum agent structure and makes every state change printable and testable.

Accept the main structure

  • State is serializable and stores no client connection or function object.
  • Actions have only tool and finish variants.
  • ToolRunner rejects unregistered names.
  • StopPolicy is independent of model output.
  • Every runtime state change emits an event.

Chapter 4 reuses these domain types, connects the first function-calling tool, and places model requests, tool execution, and result return inside the agent loop.

REFERENCES

References

  1. 01Agents SDK Quickstart | OpenAI API
  2. 02Running Agents | OpenAI API
  3. 03A Practical Guide to Building Agents | OpenAI

Series

AI AGENT Beginner's Guide

Next step

Continue with related topics

Continue along the same topic.

Browse latest news