Agentic Coding with Tool Calling
On this page
Lesson updated November 2, 2026. The original examples used Devstral, which was removed from the API on July 31, 2026. They now use Mistral Small 4 (
mistral-small-2603), and the agentic loop follows the Mistral documentation model (all tool calls in a response are processed until the final answer). Source: Function calling.
What is agentic coding?
Agentic coding changes the nature of what you ask the model to do. Instead of requesting one snippet, then another, then a third, and stitching them together yourself, you assign a complete task. The model solves it by chaining the steps on its own: exploration, planning, implementation, and verification. You remain the decision-maker for what needs to be done, but you stop micromanaging every move.
Devstral models were built for this workflow; current general-purpose models, led by Mistral Small 4, handle it through tool calling. An agent doesn’t just answer a question; it acts on the repository.
The agentic cycle
Phase 1 — Understand
The agent always starts by getting its bearings. It reads the folder and file structure, identifies files relevant to the requested task, traces dependencies between modules to see what a change might affect, and notes existing conventions—naming, test organization, and recurring patterns. This phase looks unproductive because it writes no code, yet it determines whether the resulting code will look like yours or like a stranger’s.
Phase 2 — Plan
Before writing a single line, the agent formulates a plan. It breaks the task into ordered sub-tasks, distinguishes files to modify from those to create, anticipates side effects and regression risks, and settles on a technical approach. This is when your intervention is cheapest: correcting a plan takes thirty seconds; correcting ten files you’ve already written takes much longer.
Phase 3 — Execute
Implementation unfolds the plan step by step. The agent edits existing files, creates new ones when the structure requires it, updates imports and dependencies shifted by its changes, and adjusts configuration files along the way. Consistency across these changes is exactly what a completion model cannot guarantee.
Phase 4 — Verify
Finally comes the proof that the work holds up. The agent runs the existing test suite, writes new tests for the code it just added, ensures the build passes, and reviews its own diff for errors or stray modifications. An agent that skips this phase produces plausible code, not correct code.
Worked example: fixing a bug
Suppose you tell the agent: “Users get a 500 error when they submit a form with an email containing a ’+’.” The cycle then takes this form:
1. UNDERSTAND
-> Search for files related to the form (search "email" in code)
-> Read the controller handling submission
-> Read the email validation function
-> Identify the regex rejecting '+'
2. PLAN
-> Modify the validation regex to accept '+'
-> Add a unit test for emails with '+'
-> Verify no other part of the code filters out '+'
3. EXECUTE
-> Modify src/validators/email.py: fix the regex pattern
-> Create tests/test_email_validation.py: add cases with '+'
-> Modify src/controllers/form.py: add a log for debugging
4. VERIFY
-> Run pytest -> all tests pass
-> Check diff -> changes are consistent and minimal
Notice the third line of the plan: the agent doesn’t just fix where the bug manifests; it checks that no other filter rejects the character elsewhere. This search for the root cause, rather than just the symptom, is what distinguishes an agentic fix from a spot patch.
Tool use in practice
The agent acts on its environment through function calls. You declare the available tools in the request, including their name, description, and parameter schema; the model chooses which ones to call and with what arguments.
# Example of an agentic conversation
messages = [
{
"role": "user",
"content": "Add strong password validation to the auth module."
}
]
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the content of a project file",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path"}
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "Write or modify a project file",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "run_command",
"description": "Execute a shell command",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string"}
},
"required": ["command"]
}
}
}
]
response = client.chat.complete(
model="mistral-small-2603",
messages=messages,
tools=tools,
tool_choice="auto", # default value: the model decides whether to call a tool
)
For this request, the typical sequence starts with read_file("src/auth/validators.py") to understand the existing code, continues with read_file("tests/test_auth.py") to see the format of current tests, then moves to writing: write_file("src/auth/validators.py", ...) with the added validation rule, write_file("tests/test_auth.py", ...) with the new cases, and finally run_command("pytest tests/test_auth.py") to verify. Order matters: by opening both files before touching a single character, the agent picks up your conventions and reproduces them, instead of importing ones it has seen elsewhere.
Multi-turn agentic loop
None of this happens in a single call. The heart of agentic coding is the multi-turn loop: the model proposes an action, your code executes it, and you send the result back so it can decide on the next step. You keep control of actual execution, which also serves as your safety guardrail.
import json
TOOLS = {"read_file": read_file, "write_file": write_file, "run_command": run_command}
response = client.chat.complete(model="mistral-small-2603", messages=messages, tools=tools)
messages.append(response.choices[0].message)
for _ in range(30): # guardrail: an agent must never loop indefinitely
tool_calls = response.choices[0].message.tool_calls
if not tool_calls:
break # no more tool calls: the final answer has arrived
for tool_call in tool_calls: # a response can contain multiple calls
name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
result = TOOLS[name](**arguments)
messages.append({
"role": "tool",
"name": name,
"content": str(result),
"tool_call_id": tool_call.id,
})
response = client.chat.complete(model="mistral-small-2603", messages=messages, tools=tools)
messages.append(response.choices[0].message)
print(response.choices[0].message.content)
Three details matter. The agent’s message and each tool result are reinjected into the history, paired by the tool_call_id: omit either, and the agent loses the thread, recalling the same tools in a loop. A single response can contain multiple tool calls, since parallel calls are allowed by default (parallel_tool_calls=False forbids them): process them all. Finally, cap the number of turns and have a human validate shell commands and file writes: an agent that executes everything it proposes is a risk, not a time-saver.
Key takeaways
- Agentic coding follows a cycle: understand, plan, execute, verify
- The agent acts on the repository via tool calling (
tools,tool_choice) - The multi-turn loop lets the agent iterate until full resolution
- The agent modifies multiple files consistently in a single task
- The verification phase (tests, build) is essential for reliability