AI Engineer Europe 2026
Why (Senior) Engineers Struggle to Build AI Agents
Read the talk
Why Engineers Struggle to Build AI Agents
Building reliable agents means preserving intent, letting plans change, recovering from failures, and evaluating outcomes instead of prescribing every step.
From a talk by Philipp Schmid
Before you start: Familiarity with API calls, application state, and software tests is helpful; no particular agent framework is required.
From prescribed steps to delegated goals
What changes when the software you build chooses how to carry out a task? Philipp Schmid, who introduces himself as working at DeepMind on agents with Gemini and the Gemini API, identifies five shifts from his experience with engineers inside and outside Google. The starting point is a familiar development sequence: write a specification or PRD, implement it, test it, deploy it, and let users use it.
Agent development makes observation part of the implementation loop. Define instructions, run the agent, inspect its behavior, adjust the prompt or tools, and run it again. You are improving a system’s ability to reach an outcome, including behavior you did not explicitly write.
Schmid compares the change to moving from traffic controller to dispatcher. A traffic controller regulates lights, speeds, and available roads. A dispatcher specifies a destination: get from Germany to London. The traveler might take a train, fly, or drive through the tunnel. The goal stays fixed while the route can vary. A coding agent can likewise take surprising intermediate actions and still deliver the requested result. That distinction—between specifying the outcome and prescribing every step—underlies the five examples that follow.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Text as state
A research agent proposes a plan. An interface with only accept and deny buttons treats the next interaction as a Boolean decision. But the user may want to approve the plan and modify its scope in the same response: approve the market research, focus on the US, and exclude California. The language carries both authorization to proceed and a constraint on the work.
If the interface cannot preserve both meanings, the user may have to reject the plan, answer a follow-up question, and wait for a replacement. A useful state representation instead retains the approval together with the additional instruction. For example, the response Approve the plan; focus on the US market and exclude California. should leave the research pending, with the plan approved and its scope constrained. Approval is not evidence that research has already happened.
Approve the plan and preserve the constraint
Constructed example: The exact response wording, record labels, and explicit pending execution state are teaching details constructed from the research-plan example.
Approve the plan; focus on the US market and exclude California.
Operation: Record approval and the additional scope instruction without marking the research as completed.
Task
Market research
Market research
Plan approval
Awaiting user response
Approved
Additional scope instruction
Not present
Focus on the US market and exclude California.
Research execution
Pending
Pending
Personalization has the same problem. A user may generally prefer Celsius but want Fahrenheit for cooking. A single use_fahrenheit flag loses the conditional preference; a location flag such as is_europe cannot supply it either.
| Input | What a single flag loses |
|---|---|
| Approve, but exclude California | Approval and a scope change together |
| Celsius generally; Fahrenheit for cooking | A default with a contextual exception |
Structured storage can hold these preferences. The design mistake is reducing their meaning to a structure too narrow to express them. Preserve the context the model needs to interpret the next request.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Let intent change during the conversation
Context is not limited to text: images, video, and audio can also shape what the agent should do. The next shift concerns who chooses the response. In a traditional customer-support pipeline, a cancellation request goes through an intent classifier, receives a churn label, and enters a predefined retention or cancellation workflow.
Now suppose the agent explores the request and offers an alternative. The user accepts and changes their mind about canceling. The initial classification no longer describes the conversation’s goal. An agent needs to respond to the updated meaning, rather than continue along the cancellation path because that was the first label assigned. Enumerating every offer, response, and change of intent as a stateful workflow becomes difficult. Delegating some control lets the next action follow the conversation as it develops.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Return errors to the agent
Handing over control means leaving a purely deterministic workflow. It also changes how failures should enter that workflow: an error is another input the agent can reason about. Schmid uses Go as an analogy. Go functions commonly return an ordinary result alongside an error value, and the caller checks and handles the error explicitly. A failed tool call can similarly produce information for the next decision instead of terminating the entire task.
Retrying a cheap HTTP request or product search may be acceptable. Restarting a long agent run has different consequences. Schmid asks us to imagine an agent that has already worked for five or fifteen minutes: starting over repeats the preceding computation and may discard useful context. Feed the failure back to the model so it can consider a workaround or an additional check while retaining the work already done.
A small Go adapter can make both outcomes available to the surrounding agent loop:
go
package agent
type SearchInput struct {
Query string
}
type SearchObservation struct {
Input SearchInput
Results []string
Error string
}
func ObserveSearch(
input SearchInput,
search func(string) ([]string, error),
) SearchObservation {
results, err := search(input.Query)
observation := SearchObservation{
Input: input,
Results: results,
}
if err != nil {
observation.Error = err.Error()
}
return observation
}
The caller adds this observation to the existing context. The failed query remains visible, any returned results remain available, and the error becomes material for choosing the next action. Recovery still requires a decision; returning an error as data does not itself repair the failure. The purpose is to continue from the current state instead of automatically restarting.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Trace the route, grade the outcome
Traditional unit, integration, and smoke tests often check a deterministic relationship: input A passed through code B produces output C. An agent can take different steps and produce different results from the same input. Evaluating it therefore requires asking how often it succeeds, not just whether one run passed an assertion.
Schmid’s hypothetical customer agent succeeds on the same prompt only one out of ten times—too flaky to release. This is an illustration of the reliability question, not a measured benchmark or a proposed universal release threshold.
Success also depends on the task. A research report and a customer-feedback response may need different qualitative criteria, assessed by an LLM judge or a human expert. Trace what the agent does so its behavior can be inspected, but grade whether its output satisfies the request. One user’s report might require four additional research steps and more tokens than another’s. The longer route can still be the successful one; matching a single expected sequence would miss that distinction.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Make the tool contract carry the meaning
Schmid’s fifth shift starts with a mismatch: agents evolve, while existing APIs retain assumptions built for their human developers. In a product microservice, delete_item(id) may feel self-explanatory to someone who has worked on the service for years. The agent has none of that accumulated context.
What does id identify? What happens when deletion fails? A developer familiar with the implementation may already know. Initially, the agent sees function schemas, docstrings, and tool definitions—not the code behind them. Those exposed surfaces must carry the information needed to choose and use the operation.
- Semantic names: Identify the operation and the kind of object it acts on.
- Parameter descriptions: Explain what an identifier refers to, rather than merely labeling it
id. - Failure behavior: Describe what the caller receives when the operation cannot complete.
The interface should be understandable without the experience of the team that built it. Delegating tool selection does not remove the application’s responsibility to validate an operation before executing it.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Choose what reliability must mean before release
Trusting the model and verifying its work are complementary responsibilities. Avoid forcing every request through one fixed sequence merely because that sequence is familiar. Preserve the meaning in the context and provide a way to recover when something goes wrong. Longer runs make recovery especially consequential: imperfect models can encounter unexpected situations well after useful work has accumulated.
The release decision then becomes an explicit reliability judgment. An assertion about one successful run is insufficient. Determine how frequently the agent must succeed for the user-facing task, and evaluate against that requirement. Schmid gives no single percentage that makes every agent ready; choosing the acceptable level is part of engineering the product.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Build to delete
The final implication concerns the lifetime of the software itself. Invoking The Bitter Lesson, Schmid argues that better models and agents will lead engineers to rebuild the same capabilities repeatedly. Build to delete is his engineering interpretation: expect some of today’s software to become disposable as the underlying capabilities improve.
Schmid closes by pointing readers to his companion article for a deeper treatment and code examples. The enduring responsibility is to make the current agent useful and reliable while remaining willing to replace the implementation that gets it there.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Resources
From the talk
Schmid’s companion essay develops the five engineering shifts with examples of contextual state, recovery, evaluation, and descriptive tool interfaces.
Rich Sutton’s 2019 essay on why general methods that scale with computation prevail, reproduced in a university-hosted PDF.
Further reading
- Errors are valuesArticle
Rob Pike explains how Go programs can manipulate error values and organize error handling without repetitive checks.
Updates since the talk
Current documentation for defining tools, handling function calls, and validating arguments, with Python and JavaScript examples.
Read the complete timestamped transcript
- 0:00
[upbeat music] Okay, cool.
- 0:15
Awesome. Hi. Hi, everyone. Uh, my name is Philipp. I work at DeepMind, uh, everything related to agents on Gemini or Gemini API. So if you have some questions afterwards, some concerns, some bugs, some issue, please let me know.
- 0:28
Uh, we're going to talk today ten minutes about why engineers struggle to build agents. And I see this every day internally at Google, but also externally at Google. And I brought five example on, like, what's really different to how we built traditional software a few years ago and to now how we build agents.
- 0:44
And if we, like, on a high level compare them, right? When we wrote software, uh, we created a spec, a PRD, wrote code, sometimes created tests to make sure our code works.
- 0:56
We deployed it, and then our user used it. And when building agents, things are a little bit different. Uh, we define instructions on what we want our agent to do.
- 1:05
We run it, we observe what it does. We maybe adjust our prompts, maybe we adjust our tools. We run it again, and we have, like, this iterative loop of how can we improve and make our agent way more reliable, which is very different to how we build software.
- 1:21
And, like, something I like to compare it to is, like, traditional software is more like we acted as a traffic controller, right? We had control over the streetlights, over how fast you can go, which road you can use, basically how the car drives.
- 1:34
And now with agents, we are more of a dispatcher. We tell the agent, "Hey, I want to go to London," and I'm from, like, Germany. I could use the train, I could u-- fly, I could use my car and go, like, under the water.
- 1:47
And it's more about, okay, we define the goal on what we want the agent to do, but we don't define the exact step the agent needs to take to achieve that goal.
- 1:55
And I mean, every one of you has probably seen in their coding agent that sometimes it does something very weird, but at the end it achieves the outcome, and that's what we want to do.
- 2:05
So starting with the first example, text is our new state. I mean, traditionally we had data structures, and everything was kinda mapped to Boolean or to, like, flags we could check.
- 2:16
So initially, when we created, for example, a deep research agent, a deep research agent returns a plan to you. "Okay, I'm going to research this and that." In traditional software, we might have had an accept plan, uh, or deny plan, but we couldn't catch semantic meaning.
- 2:35
And now what we have with LLMs is they can understand the semantic meaning. So for example, if I have a deep research, um, request on, like, doing some market research, I can approve the initial plan, but I can also on the same time provide additional information.
- 2:50
So maybe I want to focus on, like, the US market and ignore California. Uh, maybe I want to provide something additional and not have, like, this multiple steps, right?
- 2:59
Traditionally, I would probably said decline, and then it has a follow-up. I m-might need it to provide more input, create a new plan and continue. And another good example is everything related to memory and person-personalization we do cannot really be mapped to data structures, right?
- 3:16
The example I here-- I have is, like, um, I'm from Europe, so I mostly use Celsius. But what if I would like to use Fahrenheit for cooking, right? Previously, we might had some flags on, like, the user profile.
- 3:29
Is Celsius or is Europe or use Fahrenheit? But I couldn't, like, dynamically adjust based on the user preference, based on what they provide. So really it's all about text and context.
- 3:42
I mean, could be images, video or audio as well. But we no longer are really operating in those clear structured data concepts. The other thing is we should start handing over control and the, the trap or the example which we might had from, like, previous customer support is, like, when a user reached out, "Hey, I want to
- 4:05
cancel my subscription," I might have had a classification model which kinda classified the intent. Okay, the user wants to churn. And then I had a predefined workflow of, okay, do you try to sell it?
- 4:18
Do you cancel the subscription? But there was no, like, dynamic kinda option to, to react to it dynamically. And maybe instead of, like, um, going through the subscription cancel flow, what if your agent, like, kinda tries to understand the meaning and, like, offers something, uh, in-- except to, like, the, the subscription and the user changes their mind
- 4:41
and now you have, like, a whole different intent. And it's very hard to model all of those differences and uniqueness and to, to, like, all of those stateful workflows we had before.
- 4:53
So we need to, like, trust into the LLM or, like, hand over control that we are no longer working in those purely deterministic, um, environments. The third one is errors are just inputs.
- 5:08
So if something in your agent flow fails, we need to treat it as a normal input, as very similar to a user input. In Go, we already do this, right?
- 5:18
A function call can be an error or can be a value, and we treat them kinda equally, and we have to do this for agents very similarly. In the past, HTTP requests were very cheap.
- 5:29
When some search, some product search failed, you just rerun your request. You redid all of the work, which was okay. But now if you have, like, an agent which takes five minutes, fifteen minutes, and something in the flow breaks and you would start o-all over, you would need to sp- [coughs] You need to spend a lot of compute
- 5:49
again to, like, do all of the previous steps, and you also might lose the existing context. So we cannot, like, just start over the whole process. We need to kinda understand and treat errors differently, provide them back to the model, maybe have some other workaround, some additional checks that we-
- 6:06
Basically keep going forward in the flow and not like starting over from the beginning.
- 6:13
The fourth, uh, example or step is like we need to move from unit tests to evals. So when building software before, right, we wrote integration tests, unit tests, smoke tests, and all kind of different tests.
- 6:25
And we assume that when we provide input A for our code B, we will always get C as an output. And that's no longer the case with agent. Agents are non-deterministic.
- 6:36
We cannot always guarantee that the same input will lead to the same steps and the same result. So we need to move from unit tests to eval. We need to test how often something works because agents are only successful if they are really reliable, right?
- 6:51
If you have a customer agent and the same prompt only works one out of 10 times, it's nothing really you want to put in production, and it becomes very flaky.
- 7:00
So we need to test on evals, on how many times it passes. And compared to traditional software, results are very subjective, right? An outcome can be very different if you ask it to create a research report, if you ask it to create a customer feedback kind of scenario.
- 7:18
And we need more like qualifying, uh, feedback. Um, LLM-as-a-judge or human expert, for example, is a good way. And we always need to trace what the agent is doing, but we need to grade on the output.
- 7:29
We-- Maybe the agent decides for like one user it needs to do like four more steps to do more research than for the other user. It consumes maybe a few more tokens, but at the end, the outcome is really what we need to measure and want to measure in terms of, uh, success.
- 7:47
And then the last part is, uh, agents evolve and APIs don't. And if you have worked on the back end, and if you have built an API, you might have seen a lot of methods, API endpoints which feel very self-explaining to you, like delete item feels very self-explaining if you're working on like the product API.
- 8:10
But an agent doesn't see the code. An agent doesn't have the context and the background from all those years from you working on the API. So we need to build APIs or tools which are really agent ready, which are self-documenting with semantic interfaces.
- 8:27
I would assume if you have like a product microservice and you have a delete item endpoint with an ID, you don't need to like define a docstring what the ID is or what happens if something fails.
- 8:40
But our agents only see like the function schemas and the docstrings and the tool definitions. So on the first look, they don't really see what the delete item method does.
- 8:50
That's why we need to really adjust to, hey, we need methods, tools which are written for agents to be used and not assume long year developer expertise and people who have built the API.
- 9:04
So to, to summarize everything, we need to give trust, but we also verify. We should stop fighting the model. You should not like try to force the model into this one specific workflow with step one, do this, step two, do the other thing.
- 9:19
We need to preserve meaning. Um, everything is a context now. We no longer have those very well-defined data structures for all of our applications. We need to design for recovery.
- 9:31
Models are not perfect. Agents are not perfect, especially if we have longer running agents. There will be some very weird things happening, so you need to design for recovery.
- 9:41
We need to evaluate and don't, don't only assert. Agents are not one hundred percent reliable. We need to find the right balance between how many times our run need to be successful to provide it to the user.
- 9:53
And last but not least, build to delete. Um, the bitter lesson is what every one of us is learning is like software is disposable. We are going to rebuild many, many times the same things with better models, better agents.
- 10:08
Things will change. And, um, yes, um, it's also available on my blog. So if you want to like look a bit deeper with some code examples. And if not, if you have any questions, feel free to, to reach out to me and perfect on time.
- 10:22
Thanks. [clapping] [outro jingle]