\n\n\n\n My Agent Dev Workflow: Too Much Bloat, Too Much Opportunity - AgntDev \n

My Agent Dev Workflow: Too Much Bloat, Too Much Opportunity

📖 11 min read2,184 wordsUpdated May 21, 2026

Hey everyone, Leo here from agntdev.com! You know, sometimes I feel like I live in a constant state of “Ctrl+S, Ctrl+R” – saving my agent’s code, reloading its environment, and watching it stumble, then learn, then maybe, just maybe, do something useful. It’s a wild ride, and honestly, that’s why I love it.

Today, I want to talk about something that’s been bugging me, and at the same time, offering a ton of opportunity: the sheer bloat in our agent development toolchains. We’re all chasing the dream of the autonomous agent, but are we accidentally building Rube Goldberg machines to get there?

The Hidden Cost of “Easy” Agent Development

It feels like every week there’s a new framework, a new library, a new “turnkey solution” promising to make agent development a breeze. And don’t get me wrong, I’ve tried a lot of them. I’ve spent late nights wrestling with YAML configurations that are longer than my grandmother’s shopping list, only to realize I’ve just added three layers of abstraction between my agent and the actual task it needs to perform.

My first experience with this was about a year ago. I was trying to build a simple agent that could fetch stock prices and summarize financial news. I started with one of the popular multi-agent orchestration frameworks. It promised “seamless tool integration” and “complex workflow management.” Sounded great, right? Two weeks in, I had an agent that could call a stock API, but the news summarization part was a nightmare. Every time I wanted to tweak the prompt for the LLM, I had to dig through layers of nested dictionaries in a JSON file, then rebuild a Docker image, then redeploy. It was like trying to change a lightbulb by rewiring the entire house.

The agent itself was maybe 50 lines of Python. The framework configuration? Over 500 lines. That’s a 10x overhead just to get things running! It struck me then: are we optimizing for developer convenience at the expense of clarity, maintainability, and ultimately, agent performance?

When Less Is More: The Power of a Lean SDK

This experience led me down a different path. I started looking for ways to build agents with a much lighter touch. Not “from scratch” in the sense of writing my own LLM inference engine, but “from scratch” in the sense of directly interacting with the foundational components. And that’s where the idea of a “lean SDK” really clicked for me.

What do I mean by a lean SDK? It’s not about ditching all libraries. It’s about consciously choosing SDKs that provide direct access to core functionalities – LLM APIs, vector databases, tool execution environments – without imposing an opinionated, heavy-handed orchestration layer unless absolutely necessary. Think of it as using a sharp chef’s knife instead of a multi-tool that has a can opener, a screwdriver, and a tiny, dull blade all in one.

For example, instead of relying on a framework’s generic “LLM call” abstraction, I’d rather directly use the OpenAI Python client or the Anthropic SDK. Why? Because I get immediate access to all the parameters, all the fine-tuning options, and I understand exactly what’s happening under the hood. When something goes wrong (and trust me, it always does), I know where to look.

Practical Example 1: Direct LLM Interaction for Focused Control

Let’s say you’re building an agent that needs to draft an email based on a user’s request. A common pattern is to have a “drafting tool” that your agent calls. Many frameworks will wrap this in several layers. Here’s how I might approach it with a leaner setup using the OpenAI SDK (though the principle applies to any LLM provider):


import os
from openai import OpenAI

class EmailDraftingAgent:
 def __init__(self, api_key: str):
 self.client = OpenAI(api_key=api_key)

 def draft_email(self, recipient: str, subject: str, context: str) -> str:
 prompt = f"""
 You are an AI assistant tasked with drafting a professional email.
 Recipient: {recipient}
 Subject: {subject}
 Context for email content: {context}

 Draft the email concisely and professionally.
 """

 try:
 response = self.client.chat.completions.create(
 model="gpt-4o",
 messages=[
 {"role": "system", "content": "You are a helpful assistant."},
 {"role": "user", "content": prompt}
 ],
 temperature=0.7,
 max_tokens=500
 )
 return response.choices[0].message.content
 except Exception as e:
 print(f"Error drafting email: {e}")
 return "Could not draft email due to an error."

# Usage
if __name__ == "__main__":
 # In a real app, load API key securely, e.g., from environment variables
 openai_api_key = os.getenv("OPENAI_API_KEY") 
 if not openai_api_key:
 raise ValueError("OPENAI_API_KEY environment variable not set.")

 agent = EmailDraftingAgent(openai_api_key)
 
 recipient = "Dr. Eleanor Vance"
 subject = "Follow-up on Project Chimera"
 context = "We discussed the integration of the new data pipeline. I need to confirm the deadline for phase 2 and ask about the resources required for testing."

 draft = agent.draft_email(recipient, subject, context)
 print(f"--- Drafted Email ---\n{draft}\n---------------------")

Notice how direct this is? I’m interacting with the LLM API directly. No intermediate parsing of “tools” or “actions” defined in a separate config file. The prompt is right there, the parameters are explicit. If I want to change the model, the temperature, or add a different stop sequence, it’s a one-line change. This is the kind of control I’m talking about.

The Tooling Conundrum: Simple Functions vs. Framework-Defined Tools

Another area where bloat creeps in is tool integration. Many agent frameworks abstract tools behind decorators, Pydantic models, or specific YAML schemas. While this offers some initial ease of use, it often makes debugging harder and limits flexibility.

My preferred approach? Plain old Python functions. Your agent’s “toolset” can simply be a collection of functions that your agent knows how to call. The agent’s reasoning process (via the LLM) then decides which function to invoke based on the user’s request and its internal state.

Practical Example 2: Simple Tool Integration

Let’s extend our email agent to also be able to look up contact information. Instead of defining a complex tool schema, we just have a function:


# tools.py
def get_contact_info(name: str) -> dict:
 """
 Looks up contact information for a given person.
 Returns a dictionary with 'email', 'phone', etc.
 """
 contacts = {
 "Dr. Eleanor Vance": {"email": "[email protected]", "phone": "555-1234"},
 "John Doe": {"email": "[email protected]", "phone": "555-5678"},
 }
 return contacts.get(name, {"email": "N/A", "phone": "N/A"})

# main_agent.py (simplified)
import os
from openai import OpenAI
from tools import get_contact_info # Import our simple tool function

class AdvancedEmailAgent:
 def __init__(self, api_key: str):
 self.client = OpenAI(api_key=api_key)
 self.available_tools = {
 "get_contact_info": get_contact_info
 }

 def process_request(self, user_request: str) -> str:
 # First, let the LLM decide if a tool is needed
 messages = [
 {"role": "system", "content": "You are an AI assistant. You can use tools to find information."},
 {"role": "user", "content": user_request}
 ]

 # This is a simplified tool calling pattern. In a full agent, you'd iterate
 # and re-prompt the LLM with tool outputs.
 tool_schema = [
 {
 "type": "function",
 "function": {
 "name": "get_contact_info",
 "description": "Get contact information for a person.",
 "parameters": {
 "type": "object",
 "properties": {
 "name": {
 "type": "string",
 "description": "The name of the person."
 }
 },
 "required": ["name"]
 }
 }
 }
 ]

 try:
 response = self.client.chat.completions.create(
 model="gpt-4o",
 messages=messages,
 tools=tool_schema,
 tool_choice="auto" # Let the model decide if it needs a tool
 )
 
 response_message = response.choices[0].message
 tool_calls = response_message.tool_calls

 if tool_calls:
 for tool_call in tool_calls:
 function_name = tool_call.function.name
 function_args = json.loads(tool_call.function.arguments)
 
 if function_name in self.available_tools:
 tool_function = self.available_tools[function_name]
 tool_output = tool_function(**function_args)
 messages.append(response_message) # Add assistant's tool call
 messages.append({
 "tool_call_id": tool_call.id,
 "role": "tool",
 "name": function_name,
 "content": json.dumps(tool_output)
 })
 
 # Get a new response from the model, now with tool output
 second_response = self.client.chat.completions.create(
 model="gpt-4o",
 messages=messages
 )
 return second_response.choices[0].message.content
 else:
 return f"Error: Agent tried to call unknown tool {function_name}"
 else:
 return response_message.content

 except Exception as e:
 print(f"Error processing request: {e}")
 return "An internal error occurred."

# Usage (requires import json for tool args parsing)
import json 
if __name__ == "__main__":
 openai_api_key = os.getenv("OPENAI_API_KEY") 
 if not openai_api_key:
 raise ValueError("OPENAI_API_KEY environment variable not set.")

 agent = AdvancedEmailAgent(openai_api_key)
 
 request1 = "What is Dr. Eleanor Vance's email address?"
 print(f"Request: {request1}\nResponse: {agent.process_request(request1)}\n")

 request2 = "Draft an email to Dr. Eleanor Vance confirming the deadline for phase 2 of Project Chimera and asking about testing resources."
 print(f"Request: {request2}\nResponse: {agent.process_request(request2)}\n")

This example is a bit more involved because it shows the LLM’s tool-calling capabilities, but the core idea remains: the `get_contact_info` function is just a regular Python function. There’s no magical `Tool` class or a special registry. The agent (via the LLM) is simply given the ability to call it. This keeps your actual tool implementations clean and testable, separate from any agent-specific boilerplate.

The Agent Loop: Build, Don’t Buy

Perhaps the most significant area for bloat reduction is the agent’s core “loop” – how it perceives, thinks, and acts. Many frameworks offer pre-built loops that handle things like memory, reflection, and planning. While these can be great for quick prototypes, they often become opaque when you need to customize behavior.

I advocate for building your own agent loop, at least in its basic form. It’s not as scary as it sounds, and it gives you complete control over your agent’s cognitive architecture.

Practical Example 3: A Minimal Agent Loop

Let’s imagine a simple agent that takes a user query, tries to answer it, and if it can’t, asks for clarification. This isn’t a full-blown “thought-action-observation” loop, but it shows the principle of building your own control flow:


import os
from openai import OpenAI

class SimpleQueryAgent:
 def __init__(self, api_key: str):
 self.client = OpenAI(api_key=api_key)
 self.history = [] # Simple memory

 def respond(self, user_query: str) -> str:
 self.history.append({"role": "user", "content": user_query})

 try:
 response = self.client.chat.completions.create(
 model="gpt-4o",
 messages=[
 {"role": "system", "content": "You are a helpful assistant. If you don't know the answer, ask for more details."},
 *self.history # Include history for context
 ],
 temperature=0.5,
 max_tokens=200
 )
 agent_response = response.choices[0].message.content
 self.history.append({"role": "assistant", "content": agent_response})
 return agent_response
 except Exception as e:
 print(f"Error in agent response: {e}")
 return "I'm sorry, I encountered an error."

# Usage
if __name__ == "__main__":
 openai_api_key = os.getenv("OPENAI_API_KEY") 
 if not openai_api_key:
 raise ValueError("OPENAI_API_KEY environment variable not set.")

 agent = SimpleQueryAgent(openai_api_key)
 
 print(agent.respond("What is the capital of France?"))
 print(agent.respond("And what's its primary export?")) # Uses history
 print(agent.respond("Tell me about quantum physics.")) # Might ask for clarification

This is obviously a very basic loop, but it’s *your* loop. You can add reflection steps, tool invocation, memory management, or anything else you need without fighting a framework’s assumptions. You control the flow. You control the prompts at each step. This transparency is invaluable when debugging complex agent behaviors.

Actionable Takeaways for Lean Agent Development

  • Question Every Abstraction: Before pulling in a new framework or library, ask yourself: “Does this simplify my core task, or does it add another layer of complexity I’ll have to manage?” Sometimes a few lines of direct SDK code are better than hundreds of lines of framework configuration.
  • Prioritize Direct SDK Access: For core components like LLM calls and vector database interactions, try to use the provider’s native SDKs directly. This gives you maximum control and clarity.
  • Embrace Simple Functions for Tools: Your agent’s tools don’t need to be wrapped in complex framework-specific classes. Plain Python functions are often sufficient and much easier to test and maintain.
  • Build Your Own Core Loop (Initially): For the agent’s decision-making process, start with a simple, custom loop. Only introduce orchestration frameworks if your complexity genuinely demands it, and even then, choose ones that are modular and allow you to swap out components.
  • Focus on Observability: A leaner codebase is inherently easier to observe. When you have fewer layers of abstraction, it’s simpler to log exactly what’s happening at each step of your agent’s execution, which is critical for debugging and improvement.

Look, I’m not saying throw out every framework. They have their place, especially for rapid prototyping or if you’re building a very specific type of agent that perfectly fits a framework’s paradigm. But as agent developers, our goal is to build intelligent, reliable systems. Sometimes, the path to intelligence isn’t paved with the most convenient, pre-packaged solutions, but with a deeper understanding and more direct control over the moving parts.

So, next time you’re starting a new agent project, take a moment. Could a simpler SDK and a bit of custom code get you further, faster, and with fewer headaches down the line? I’d bet it could.

Happy building, and may your agents be ever lean and effective!

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

Learn more →
Browse Topics: Agent Frameworks | Architecture | Dev Tools | Performance | Tutorials
Scroll to Top