Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 1: From Completion to Control Loop

Northline sells headphones. Jordan Lee bought a pair — order O-1001. Eight days later, carrier tracking is still “in transit.” Jordan writes support: Where is my order?

Maya takes the ticket. She could reply by hand once. She cannot do that for every stalled shipment. She opens a case in ShopOps — the system this book builds — and asks it to reach out: pull the live order and shipment, draft a status email to Jordan, and only escalate to refund or reship if the facts justify it.

ShopOps writes the draft. A model proposes the wording. Maya (or a later approval queue) decides whether anything is sent. Jordan only receives mail after that gate.

Jordan asks → Maya opens O-1001 → load facts → draft email → approve → send (or escalate)

That chain is the product. Skipping it is the trap.

If Maya’s request becomes a single model call — “Write a helpful email about this delayed order” — you get a completion. Maybe a good one. That is still not an agent, and it skips the chain above.

Why not? Because nothing in that call is grounded. The text is not tied to a live order or shipment record. Nothing checked whether a refund or reship is even on the table. Nothing decided whether the system is allowed to draft versus send. And if you later bolt on tools without a step budget, nothing stops a confused policy from calling tools forever.

ShopOps’s first rule is blunt on purpose: the system may draft customer email; it may not send without an approval path. That is a control problem. Prompting alone will not carry it.

First principles

A completion is one model call → one continuation.

A chain is a fixed sequence of calls.

A workflow is code-owned control flow; the model fills in steps.

An agent, in this book, is a stateful decision system under uncertainty: it loops over an environment, chooses actions from a constrained space, updates state from observations, and terminates.

observe → decide → act → observe → … → terminate

Why “terminate” is in the sentence: without it you do not have an agent. You have a heater for tokens.

TermMeaning
EnvironmentTools, OMS, carrier APIs, email gateway, humans, clocks
Observation (o)What came back after an action
Action (a)Something from a closed (or tightly constrained) action space
State (s)Structured case state — not only chat text
Episode / caseOne bounded unit of work (here: O-1001 / Jordan)
Step budgetHard cap so while True cannot burn the company

Drop these misconceptions early:

  • A tool-calling model is not, by itself, a production agent.
  • More autonomy is not always better. For customer messages and refunds, less autonomy is often the product.

Concrete example: first contact on O-1001

Same cast. Jordan asked. Maya opened the case. We want the smallest loop that does the middle of the story:

  1. Load Jordan (customer record).
  2. Read order O-1001 + shipment status.
  3. Draft the email to Jordan.
  4. Propose send — then park for Maya’s approval instead of emailing.

No vendor SDK. Mock model. A few tools. Hard max steps. That is enough to teach the shape.

Diagram

What people confuse:

┌─────────────────────────────┐
│ Autonomous fleet (later)    │  broad rights — not our default
├─────────────────────────────┤
│ Agent = control loop        │  state + policy + env + terminate
├─────────────────────────────┤
│ Workflow / chain            │  code owns the order of steps
├─────────────────────────────┤
│ Completion                  │  one sample
└─────────────────────────────┘
flowchart TD
  Start([case O-1001]) --> Decide[policy: choose action]
  Decide --> Act[execute tool / env]
  Act --> Obs[observation]
  Obs --> Update[update state]
  Update --> Check{done or max_steps or awaiting_approval?}
  Check -->|no| Decide
  Check -->|yes| Stop([park or finish])

Notice: parking on approval is a success mode, not a crash. The loop must know how to stop spinning.

Implementation

cd manuscript/code/shopops
pip install -e ".[dev]"
PYTHONPATH=. python examples/ch01_loop.py

Core loop (shopops/loop.py):

def run_loop(state, policy=None, env=None):
    policy = policy or scripted_policy
    env = env or mock_env
    actions = []
    while not state.done and state.step < state.max_steps:
        if state.awaiting_approval:
            break  # park — do not spin
        action = policy(state)
        actions.append(action)
        observation = env(state, action)
        transition(state, action, observation)
        if action.kind in ("done", "escalate"):
            break
    return LoopResult(state=state, steps=state.step, actions=actions)

Scripted policy (stand-in for a tool-calling model) — read this as control flow, not as “AI magic”:

def scripted_policy(state: State) -> Action:
    if state.customer_name is None:
        return Action(kind="get_customer")
    if state.order_total_cents is None:
        return Action(kind="get_order")
    if state.shipment_status is None:
        return Action(kind="get_shipment")
    if state.draft_email is None:
        return Action(kind="draft_email", args={"body": f"Hi {state.customer_name}, ..."})
    if not state.email_sent and not state.awaiting_approval:
        return Action(kind="send_email", args={"body": state.draft_email})
    return Action(kind="done")

Why the environment blocks send without an approval ticket: so the loop can learn the difference between “proposed” and “done.” Typical O-1001 output:

steps: 5
actions: ['get_customer', 'get_order', 'get_shipment', 'draft_email', 'send_email']
customer: Jordan Lee
order_total_cents: 12900
shipment_status: in_transit_stalled
awaiting_approval: True
email_sent: False

That awaiting_approval: True line is the point of the chapter.

Failure modes

Bare while True. No max_steps, no terminal actions → infinite cost and duplicate side effects.

Completion-as-agent. One-shot email skips order lookup, shipment status, and approval.

Tool loop without park. Send “fails,” policy retries send every tick → thrash the gateway and the review queue.

State only in chat. “Did we already draft?” becomes a linguistic accident.

Autonomy inflation. Giving the model real send_email / issue_refund credentials because “agents should act” is how you get fraud and angry customers.

Production considerations

  • Cap steps per case; metric when the cap hits.
  • Separate propose tools from commit tools (different scopes).
  • Prefer park (awaiting_approval) over busy-wait.
  • Log actions and observations (full traces in Part VII).
  • Keep the loop shape stable when you swap mock LLM → real client.

Chapter summary

  • Jordan asks about O-1001; Maya opens ShopOps; the system drafts; approval gates send.
  • A completion is not an agent; an agent is a control loop that can stop.
  • First contact rule: draft yes, send only through approval.
  • Smallest useful loop: customer → order → shipment → draft → propose send → park.
  • Code: shopops/loop.py, examples/ch01_loop.py.

Exercises

  1. Add escalate when days_since_shipped exceeds a threshold; assert reason escalated.
  2. Force an infinite tool loop (get_order forever); measure steps until max_steps.
  3. Rewrite as a fixed 3-step workflow (no while); list what you lost.
  4. Random legal actions for 100 runs; report how often the case parks cleanly.

References

  • Yao et al., 2022. ReAct. arXiv:2210.03629 — reason/act as a research pattern; we treat the systems wrapper as the product.
  • Anthropic. Building Effective Agents. 2024. [VERIFY SOURCE] — workflows vs agents framing.