Executive Summary
Natural-language inquiries are ambiguous. Business workflows such as refunds, shipping, and data updates, however, must not operate in “probably this order.”
The key to resolving this contradiction is neither delegating everything to an LLM nor avoiding LLMs altogether.
Let the LLM make semantic judgments, receive the results through a schema, and use Python and graph edges to execute only permitted routes.
The shipping inquiry agent from Build With Google Day 1 Lab 2 demonstrates this boundary with a small amount of code. This article examines the development lifecycle enabled by Agents CLI and the structure of an ADK Graph in the following order:
- Why Scaffold, Lint, Playground, and CLI form one continuous workflow
- What Nodes, Edges, State, and Routes are
- How to separate probabilistic classification from deterministic control
- How to think about Safety, Liveness, Failure, and Safe defaults
Where We Are in the Series
This five-part series explores the question: “AI has become extraordinarily intelligent. Can we truly entrust it with real work?”
- What Is Agentic Engineering?
- Antigravity as a Workplace
- Agent Skills as Operating Manuals
- Process Design with Agents CLI and ADK Graph (This Article)
- Evaluation Through Output and Trajectory
Connecting from the Previous Article—A Manual Alone Cannot Control a Process
In the previous article, we described a Skill as a reusable operating manual.
Once a task involves multiple stages, however, it is not enough to specify “what to do.” We must also specify “where the process is allowed to go next.”
Suppose we assign the following task to a new employee at a shipping company:
- Read the customer’s inquiry.
- Determine whether it is a shipping-related question.
- If it concerns shipping, hand it over to the FAQ representative.
- If it is unrelated, politely explain that it falls outside the scope of support.
Semantic judgment requires flexibility. At the same time, an unrelated inquiry must not be sent arbitrarily to some other high-privilege operation.
In other words, in addition to reusable procedures, we need a Workflow that controls state and transitions.
Separating a New Employee’s Judgment from the Company’s Approval Path
“When will my package arrive?” is clearly about shipping. But what about the following inquiries?
- “Will the item I ordered yesterday arrive by the weekend?”
- “I entered the wrong address. Can I still change it?”
- “The box arrived crushed, and I’d like to discuss it.”
These are difficult to classify through keyword matching alone. They require semantic judgment that takes context into account, which is something LLMs handle well.
An LLM’s output, however, is probabilistic. Even with the same input, its phrasing or intermediate results may vary. Instead of fully delegating the decision by telling the LLM, “Freely choose and execute the next Python function,” we convert its judgment into typed data and let the code select a permitted route.
The boundaries shown in the diagram are as follows:
| Layer | Responsibility | Characteristic |
|---|---|---|
| LLM classifier | Interpret the meaning of the inquiry | Flexible but probabilistic |
| Typed Schema | Fix the shape of the judgment result | Typed handoff contract |
| Python route | Map a label to a permitted route | Deterministic |
| Graph edge | Enumerate the Nodes that may be visited next | Set of permitted transitions |
| Handler | Execute the processing for that route | Localized responsibility |
In other words, we do not eliminate the LLM’s uncertainty. We narrow the area in which that uncertainty is allowed to exist.
Agents CLI Is Not Just a “Generation Command”
In Lab 2, we first set up agents-cli and register seven development Skills:
google-agents-cli-adk-codegoogle-agents-cli-deploygoogle-agents-cli-evalgoogle-agents-cli-observabilitygoogle-agents-cli-publishgoogle-agents-cli-scaffoldgoogle-agents-cli-workflow
We also confirmed on the VM that all seven were present in the Skill directory.

This configuration shows that the CLI’s role extends beyond generating templates.
Express the requirements in words
↓
Create the structure with Scaffold
↓
Read and revise the Graph code
↓
Reduce static issues with Lint
↓
Observe interactions and state in Playground
↓
Run one-off executions with the CLI and connect them to automation
↓
Expand into Eval / Observability / Deploy / Publish
Within this lifecycle, Lab 2 completes one full cycle through Scaffold, Graph, Lint, Playground, and one-off CLI execution.
In other words, Agents CLI is not a tool for generating an Agent once. It is an entry point into an iterative loop of building, reading, inspecting, and running.
Scaffold—Turning Natural-Language Requirements into an Inspectable Structure
In Lab 2, we request a Customer Support Agent for a shipping company using natural language. The core requirements are:
- First classify whether the inquiry is related or unrelated to shipping
- If it is shipping-related, send it to the Shipping FAQ Agent
- If it is unrelated, decline politely
- Do not perform Deployment
Scaffold generates a Project template from this Intent. What matters is that the generated result can subsequently be read as code.
In the Project on the VM, we mainly confirmed the following structure:
customer-support-agent/
├─ app/
│ ├─ agent.py # Agent, Node, Edge, Workflow
│ └─ schemas.py # Classification result type
├─ tests/
│ ├─ unit/
│ └─ integration/
├─ pyproject.toml # Dependencies and development settings
└─ agents-cli-manifest.yaml
When something created from natural language is converted into files, types, and tests like these, humans can inspect how the Intent was interpreted.
In other words, the value of Scaffold lies less in creating files quickly than in grounding an ambiguous request in a reviewable structure.
The Four Components of an ADK Graph
The word Graph may sound difficult, but it becomes straightforward when translated into a company’s business workflow.
Node—One Task
A Node is a unit of processing, such as classifying, answering, or declining. A Node may use an LLM, or it may run as an ordinary Python function.
Edge—A Permitted Path Forward
An Edge connects Nodes. An Edge that does not exist in the Graph normally cannot be selected as an execution path.
State / Context—What Is Currently Known
State is information passed between stages, including inputs, intermediate results, and the route.
Route—The Destination of a Conditional Branch
Depending on the classification result, the process selects a label such as shipping or unrelated.
In simplified form, the Lab Project looks like this:
START
↓
classifier_agent LLM determines whether the inquiry is shipping-related
↓
route_decision Python sets the route label
├─ shipping → shipping_faq_agent
└─ unrelated → decline_node
Here, the Graph is not a diagram that visualizes the LLM’s reasoning. It is a program structure that explicitly defines executable processing and permitted transitions.
In other words, a Node is a task, an Edge is a permitted movement, State is carried-over information, and a Route is a branch destination.
Receiving LLM Output Through a Schema
Passing free-form text directly into control logic creates problems with variations in wording.
“This is about shipping.”
“shipping-related”
“This is a question about the delivery date.”
All of these make sense to a human, but they are unstable as branching conditions in code.
In schemas.py on the VM, the classification result was defined roughly as follows:
class QueryClassification(BaseModel):
is_shipping_related: bool
category: str
reasoning: str
This type is then specified as the Classifier’s output_schema.
classifier_agent = LlmAgent(
# ...
output_schema=QueryClassification,
)

What the Schema primarily guarantees is the “shape” of the result. It creates a boundary that requires is_shipping_related to be a boolean and requires fields such as category and reasoning to exist.
A correct type does not necessarily mean correct semantics, however. The form is_shipping_related=False may be valid even if a shipping question was misclassified. Type validation and semantic evaluation are separate concerns.
In other words, a Schema is not magic that makes an LLM produce the correct answer. It is a contract for safely handing probabilistic output to a deterministic program.
Returning the Route to Ordinary Python
In the implementation on the VM, route_decision, which receives the classification result, was written as ordinary Python.
is_shipping = node_input.get("is_shipping_related", False)
route = "shipping" if is_shipping else "unrelated"
ctx.route = route

These few lines contain important design decisions:
- Keep semantic classification inside the LLM Agent.
- Deterministically map the resulting boolean to one of two labels for all subsequent control.
- Limit the value assigned to
ctx.routeto eithershippingorunrelated. - Fall back to the
Falsebranch if the expected field is missing.
The Graph’s edges then map each label to the next Node.
edges = [
("START", classifier_agent),
(classifier_agent, route_decision),
(route_decision, {
"shipping": shipping_faq_agent,
"unrelated": decline_node,
}),
]

Even if the LLM determines that an inquiry is about shipping, the process cannot proceed through the normal route to a refund-execution Node if no Edge to that Node exists in the Graph.
Of course, the Graph alone does not automatically make the entire system safe. If each Handler has access to dangerous Tools, Permissions and input validation are also necessary. Even so, the ability to enumerate control paths is highly valuable.
In other words, the LLM determines “what type of inquiry this is,” while the code determines “where that type of inquiry is allowed to go.”
What Do Lint, Unit Tests, Playground, and CLI Verify Separately?
A single successful execution does not mean that everything is correct. Lab 2 uses multiple inspection methods because each one identifies different problems.
Lint
Lint reduces issues that machines can readily detect before execution, including syntax, formatting, types, and imports. It does not guarantee that the business response is correct.
Unit Test
A Unit test isolates the contract of a small function. On the VM, there was a test verifying that is_shipping_related=True results in ctx.route == "shipping" and that False results in "unrelated". The test passed.

This does not test the LLM’s accuracy. It tests the deterministic mapping applied after the classification result is received.
Playground
Playground lets us observe inputs, intermediate state, branches, and responses while holding a conversation. The Lab guide also covers hot reload, which immediately reflects code changes. Because we did not independently verify that feature this time, it is discussed here only as part of the development experience described in the guide.
Single-Turn CLI
The Single-turn CLI runs one input non-interactively. This makes it easier to incorporate execution into scripts or CI instead of relying only on a person testing through a screen.
On the VM, we ran two inputs—one related to shipping and one unrelated—and confirmed that they followed the shipping and unrelated branches, respectively.

This proves that two representative examples passed. It is not statistical proof that every possible phrasing will be classified correctly.
In other words, Lint examines form, Unit tests examine local contracts, Playground examines interactive behavior, and CLI provides repeatable execution.
Going One Level Deeper: Reading a Graph in Terms of Safety and Liveness
When evaluating a Workflow at a more advanced level, we consider two types of properties rather than asking only whether it produced the correct answer.
Safety Property—Nothing Bad Happens
- An unrelated inquiry does not enter shipping-specific processing
- A mutation Tool is not called before approval
- An unknown route label does not flow into a high-privilege Node
- A failed intermediate result is not passed to the next stage as a success
Graph edges, schemas, Permissions, and checks for prohibited Tools support this property.
Liveness Property—Valid Work Moves Forward
- A legitimate shipping question reaches an answer without entering an infinite loop
- After a temporary failure, the process proceeds to a defined retry or human review
- There is no dead end where execution stops because no Node is reachable
If an Agent merely stops safely, it can become an Agent that rejects everything. We need both safety and the ability to complete work.
Failure Boundary—Where Failures Are Handled
| Failure | Detection Point | Example of Safe Handling |
|---|---|---|
| LLM call failure | Classifier | Escalate to a human after a finite number of retries |
| Schema mismatch | Type boundary | Stop without passing the result to the Route |
| Unknown label | Router | General, human review, or rejection |
| Handler failure | Each Node | Check whether a change occurred before retrying |
| Insufficient Tool permissions | Tool boundary | Report the issue without arbitrarily expanding permissions |
A Safe default does not mean “just provide a general answer for now.” It means choosing a default that does not proceed to an irreversible operation when a failure occurs. Depending on the business domain, the safe choice may be a General route, Human review, or explicit rejection.
Key Takeaways
- Flexibility and strictness can be assigned to different components. The LLM determines meaning, while the Schema and code convert that judgment into a permitted route.
- A Graph is an execution contract, not a reasoning diagram. Nodes, Edges, State, and Routes explicitly define the possible transitions.
- Agents CLI supports the development loop. Scaffold, Lint, Playground, and the Single-turn CLI each identify different kinds of failures.
Next Time
The shipping-related and unrelated inputs followed their expected routes. Is that enough to trust the Agent?
Even if the final answer is correct, the Agent may have called an unnecessary Tool along the way. Even if it followed the correct route, the content of its response may still be wrong. A single successful run tells us nothing about differently phrased inputs or behavior during failures.
Next time, we will separate Output from Trajectory and turn an Agent’s “it worked” into inspectable evidence.