Workspace/Lesson workspace
Loading progress
Foundations40 min

An agent chooses actions inside a contract

Lesson 1 of 3
How to study this lesson

Read one section, trace the worked example, and try the knowledge check. Bookmark saves a shortcut; notes appear in My notebook. Mark lesson complete is your own assessment and does not mark its lab passed.

By the end, you can

  • Distinguish a fixed workflow from model-selected next actions.
  • Write a bounded task and verifiable completion condition.
  • Separate policy proposals, execution, and observations.

Choose a task small enough to verify

A first agent should have a narrow objective and a small action space. Consider a catalog assistant that answers whether a named item is available using a read-only inventory lookup. Its completion condition is concrete: the final answer must identify the requested item and agree with the observed stock result, or explain that the item was not found. It cannot buy stock, change inventory, or browse arbitrary systems.

A fixed workflow always follows predetermined steps, such as parse request, fetch record, format answer. An agent permits a policy, often a model, to select the next action based on observations. Both can be useful. The engineering question is whether flexible choice improves the target task enough to justify additional latency, cost, and failure modes. Start with the fixed path as a baseline.

Separate the policy from the controller

The policy proposes an action, such as lookup with key A7 or finish with an answer. The controller checks whether the action is allowed in the current state, whether its arguments are valid, and whether budget remains. The tool executes a permitted action and produces an observation. The next policy decision can use that observation, but it does not inherit unrestricted authority from it.

This separation lets you substitute a deterministic policy while testing the controller. A fixture policy that requests a lookup and then finishes is not a language model, but it exercises the same state and authorization boundaries. A later model adapter can provide proposals through the same interface. Avoid letting a model-specific response object become the entire application state; normalize what the controller needs.

Budgets are part of the task definition

A bounded agent has limits on model rounds, tool calls, elapsed time, and resource use. These limits should be enforced in code, because an instruction asking the model to stop after three attempts is not an execution boundary. Decide what the user receives when a budget expires: a partial result with evidence, a clear failure, or a request for more context.

Suppose the task allows one lookup. The policy first asks for A7, observes eight units, then asks for an unrelated B2 lookup. The controller should reject the second proposal before execution. A completion check must also prevent the policy from claiming B2 was checked. Budget exhaustion and unsupported claims are different failures, so record both when diagnosing an agent's behavior.

Completion requires evidence the controller can inspect

A fluent final message is not evidence that the task succeeded. Define checks linked to tool observations. In the catalog case, a structured final result can include sku, found, available, and source_call_id. The controller verifies that source_call_id refers to a successful lookup for that same SKU and that the quantity matches the observation. Human-readable text can then be rendered from the validated structure.

This pattern is stronger than asking a second model whether the first answer sounds correct, though model evaluation can still help with tasks that lack exact checks. Some objectives need human review or uncertainty statements. The lesson's narrow task was chosen because its success condition can be independently verified without reading hidden model reasoning or trusting a self-reported success flag.

Measure autonomy against a baseline

The example runs a deterministic policy through a two-step controller and checks its final quantity against observed data. It is a control-loop simulation, not a model-powered agent. Its value is that you can trace the exact boundary between a proposed action and an executed action. The mini project later exposes a similar bounded service behind an API.

When adding a real model, compare it with the fixed workflow on identical requests. Count successful verified answers, unnecessary tool calls, unsupported claims, latency, and budget failures. Flexible behavior is useful when the right next step varies with the input; it is unnecessary overhead when one reliable lookup always suffices. A portfolio report should explain why the chosen amount of autonomy fits the problem, supported by examples and measurements.

Work through the code

A deterministic policy first proposes one local read and then a structured final result. The controller enforces a tool budget and compares final fields to the observation. There is no model or network request. Replace policy with a normalized model adapter while preserving the controller checks.

m06_lesson_1.py
python
def policy(observation):
    if observation is None:
        return {"type": "lookup", "sku": "A7"}
    return {"type": "finish", "sku": observation["sku"],
            "available": observation["available"]}

def run(max_calls=1):
    stock = {"A7": 8}
    observation = None
    calls = 0
    for _ in range(2):
        proposal = policy(observation)
        if proposal["type"] == "lookup":
            if calls >= max_calls or proposal["sku"] not in stock:
                raise ValueError("lookup rejected")
            calls += 1
            observation = {"sku": proposal["sku"], "available": stock[proposal["sku"]]}
        elif proposal["type"] == "finish":
            if observation is None or any(proposal[k] != observation[k] for k in ("sku", "available")):
                raise ValueError("unsupported final result")
            return {"status": "completed", "calls": calls, "result": observation}
    raise RuntimeError("round budget exhausted")

print(run())
EXPECTED / ILLUSTRATIVE OUTPUT
{'status': 'completed', 'calls': 1, 'result': {'sku': 'A7', 'available': 8}}

Run Python snippets locally with the prerequisites named above. The module coding lab runs directly in your browser.

Pause and reason

Your agent’s final JSON says available=10, but its only lookup observed available=8 for the requested SKU. The final JSON is schema-valid. What should the controller do and what should be recorded?

Check your understanding

What makes the policy/controller separation useful?

Your notes

Explain the mechanism in your own words. Add a failure you want to test.

Saved notes appear in your notebook

Go deeper with primary sources

Practice this module