I Shipped on Google's Agent Framework, Then Deleted It for 30 Lines

I adopted Google's Agent Development Kit, shipped a feature on it, then pulled it out and replaced the agent with about 30 lines of plain Python. A field report on when an agent framework is the value and when it is just ceremony.

A masterless samurai sits reading a book titled Design Patterns while an older master watches over his shoulder, composited after Akira Kurosawa's Yojimbo (1961)
After Yojimbo (1961), dir. Akira Kurosawa · Toho

I shipped a feature on Google’s Agent Development Kit. It ran in production. Then I deleted the framework and replaced the agent with about 30 lines of plain Python.

This is not a story about evaluating a framework and passing. I adopted it. I shipped on it. Then I took it out. The deleted code is still in the git history: an ADK agent, its callbacks, its tool wrappers, all gone in one commit.

What I built first

The feature is an agent that answers questions about marketing data by writing SQL. You ask in plain English, it writes the query, runs it against the warehouse, and answers. To build the loop around that, I reached for ADK. It gives you the agent loop, the tool-calling, and lifecycle hooks that fire around each tool call. It shipped, and it worked.

I want to be clear that it worked. This is not a bug report. It is a question about whether the framework was carrying its weight, and the answer turned out to be no.

The part I cared about was never the loop

The agent loop is the least interesting code in the system. The parts I actually cared about were three: the safety checks that block a dangerous or cross-client query before it runs, the audit ledger that records every action the agent takes, and the eval set that tells me whether the thing is still correct after a change.

None of those needed ADK. They are my logic, my rules, my problem. But the framework had quietly reached into them. My safety checks lived inside ADK’s before_tool_callback, the hook it hands you to inspect a tool call before it executes. So the single most important code in the system, the part that decides whether a query is allowed to touch real data, had become a guest inside the framework’s lifecycle.

That is the moment I started writing the deletion ticket. When the framework owns the part you most need to own, the convenience has turned into a liability.

Deleting it

I pulled ADK out. The replacement agent is a plain Python loop: call the model, get back a tool call, run it, feed the result in, repeat until it answers. About 30 lines. It is simpler than the ADK wrapper it replaced, and I can read the whole thing on one screen.

Writing the loop was the easy part. The real work was un-coupling the safety logic from before_tool_callback and lifting it into a standalone module that any caller can use, framework or not. Once the safety checks stood on their own, the loop around them barely mattered. That was the tell. The valuable thing was never framework-shaped to begin with.

The shape is small enough to put on the page. A provider interface so the model stays swappable, the safety check as a plain function the loop calls before any tool runs, and the loop itself:

# The boundary I kept: domain logic never imports the framework.
# A thin provider interface, a standalone safety check, a plain loop.

class LLMProvider(Protocol):
    def complete(self, messages: list[Message], tools: list[Tool]) -> Reply: ...

def is_allowed(call: ToolCall) -> Decision:
    # the safety rules live here, callable by anyone, framework or not
    ...

def run_agent(provider: LLMProvider, tools: list[Tool], question: str) -> str:
    messages = [system_prompt, user(question)]
    while True:
        reply = provider.complete(messages, tools)
        if not reply.tool_call:
            return reply.answer
        if is_allowed(reply.tool_call).blocked:
            messages.append(refusal(reply.tool_call))
            continue
        messages.append(run_tool(reply.tool_call))

The safety check is the point. It is a plain function now, not a framework callback, so the same rule runs whether the loop above calls it or anything else does. Nothing in my domain code imports the agent SDK. That is the whole boundary, and it fits in one screen.

What I kept

I kept everything that was actually worth keeping, and now it belongs to me. The safety checks, the audit ledger, the eval set, all framework-agnostic. The agent talks to the model through a thin provider interface, so changing the underlying LLM is a config change, not a rewrite. The system is no longer betting on one vendor’s agent SDK, because it no longer depends on one at all.

That is the quiet payoff, and it is worth more than the 30 lines. The day a better model ships, or a provider changes its terms, I move. Nothing in the safety or audit layer has to know it happened.

When a framework is actually worth it

I want to be fair, because the lesson here is not that frameworks are bad. A framework earns its keep when its abstractions are the value you could not easily build yourself. ADK’s pitch is the agent loop and the tool lifecycle. For my case, that was 30 lines I could write and own outright. The value in my system lived somewhere the framework did not reach.

So the test I use now is short. Is the abstraction the thing I need, or a wrapper around the thing I need? A framework that hands you a swappable orchestrator is answering a different question than one that hands you a 30-line loop dressed up as a platform. If the framework ends up owning the part that matters, the way my safety checks ended up living inside a callback, that is a warning, not a feature.

What it comes down to

Most of my framework decisions now are subtraction, not addition. The interesting code in a production agent is rarely the agent loop. It is the boring layer underneath: the checks, the records, the proof that the system did what it claimed. That layer should be yours, and it should not need anyone’s permission to run.

I’d take 30 lines I own over an abstraction I rent, at least for the part where an agent stops being a demo and starts touching a real warehouse with real money attached. A framework that owns that part is not saving you time. It is borrowing your trust.