This article is published in English.
AI Agents for Future Engineers: Memory, Tools, and Control Loops
A practical map of agent building blocks—planning, tools, memory, and evaluation—without hype vocabulary standing in for design.
This walkthrough rebuilds an operable path for: Everything Future AI Engineers Need to Know About AI Agents. Focus on contracts, checks, and code you can drop into a repo without guessing intent. For Overview, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
How Agents Take Action
For How Agents Take Action, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
import requests
def search_web(query: str) -> list[dict]:
response = requests.get(
"https://serpapi.com/search",
params={"q": query, "api_key": "YOUR_API_KEY", "num": 5},
)
results = response.json()["organic_results"]
return [
{"title": r["title"], "url": r["link"], "snippet": r["snippet"]}
for r in results
]
results = search_web("best sourdough recipe")
for r in results:
print(r["title"], "-", r["url"])
import anthropic
client = anthropic.Anthropic()
# The menu of tools the model can choose from
tools = [
{
"name": "web_search",
"description": "Search the web for current information.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"],
},
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather in Seattle right now?"}],
)
print(response.content)
# [ToolUseBlock(name='web_search', input={'query': 'Seattle weather today'})]
# 1. Parse the LLM response to find the tools it wants to run
tool_calls = [block for block in response.content if block.type == "tool_use"]
# 2. Run the functions directly, OUTSIDE of the LLM
# (this is our search_web function from earlier -- plain Python,
# the model never sees this code)
tool_results = []
for call in tool_calls:
if call.name == "web_search":
output = search_web(call.input["query"])
tool_results.append(
{
"type": "tool_result",
"tool_use_id": call.id,
"content": str(output),
}
)
# 3. Hand the results back -- from the model's perspective,
# the answer just shows up in the chat
final = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What's the weather in Seattle right now?"},
{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results},
],
)
print(final.content[0].text)
# "It's 62 and cloudy in Seattle."
Multi-Step Tasks
For Multi-Step Tasks, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments. Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
# The ReAct loop
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
# If the model didn't ask for any tools, it's done -- that's its final answer
if response.stop_reason != "tool_use":
break
# Otherwise: run the tools, append the results, and go around again
tool_results = []
for block in response.content:
if block.type == "tool_use":
output = run_tool(block.name, block.input)
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": str(output)}
)
messages.append({"role": "user", "content": tool_results})
print(response.content[0].text)
# "Booked it into your calendar -- cheapest flight was the 9:15am Alaska
# departure Friday at $138. Event added from 9:15am to 11:30am."
Reliable Outputs
For Reliable Outputs, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive. For Reliable Outputs, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
system_prompt = """You have access to a web_search tool.
To use it, respond with JSON in this format:
{"name": "web_search", "input": {"query": "..."}}
CRITICAL: You MUST respond with ONLY valid JSON. NO other text.
NO markdown. NO code fences. NO explanations before or after.
Your ENTIRE response must be parseable by json.loads().
DO NOT FORGET THE COMMAS. CHECK YOUR BRACKETS.
If you output anything that is not valid JSON, the system WILL CRASH.
THIS IS EXTREMELY IMPORTANT. VALID JSON ONLY.
"""
High Quality Inputs
For High Quality Inputs, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Checkpoint after expensive model calls so a retry does not re-bill the same work.
tools = [
{
"name": "web_search",
"description": "Search the web for current information.",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
{
"name": "add_calendar_event",
"description": "Add an event to the user's calendar.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start_time": {"type": "string"},
},
"required": ["title", "start_time"],
},
},
# ...plus read_email, send_email, get_flights, book_flight,
# read_file, write_file, run_code, and 20 more
]
Trusting Your Agent
For Trusting Your Agent, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments. Checkpoint after expensive model calls so a retry does not re-bill the same work.
Operational checklist
For Operational checklist, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
Add a smoke test that exercises the critical path in CI with fixtures when budgets allow.
Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
Before promoting the stack, freeze versions, capture a golden transcript for the critical path, and confirm rollback steps. Shared environments need rate limits, tenancy checks, and a clear owner for secret rotation. Prefer boring reliability over clever one-off demos.
For hardening note 0, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments.
Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
For hardening note 1, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
For hardening note 2, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
Separate planning from tool execution. The planner proposes; the executor mutates; the verifier checks outcomes against the goal.
For hardening note 3, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.
Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
For hardening note 4, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
For hardening note 5, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments.
Separate planning from tool execution. The planner proposes; the executor mutates; the verifier checks outcomes against the goal.
For hardening note 6, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish.
Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
For hardening note 7, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
For hardening note 8, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.
Separate planning from tool execution. The planner proposes; the executor mutates; the verifier checks outcomes against the goal.
For hardening note 9, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
For hardening note 10, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
For hardening note 11, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish.
Separate planning from tool execution. The planner proposes; the executor mutates; the verifier checks outcomes against the goal.
For hardening note 12, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.
For hardening note 13, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.
Checkpoint after expensive model calls so a retry does not re-bill the same work.
For hardening note 14, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Separate planning from tool execution. The planner proposes; the executor mutates; the verifier checks outcomes against the goal.
For hardening note 15, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments.
Bound tool schemas tightly. Wide free-text args invite injection and make audits expensive.