ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

AI Agent Architecture

Design and build autonomous AI agents that can plan, reason, and take action. Covers agent loops, tool use, memory systems, multi-agent orchestration, guardrails, and the patterns that make AI agents reliable and controllable.

AI agents are LLMs that can take actions in the real world — call APIs, write code, query databases, and interact with tools. Instead of a single prompt-response, agents operate in a loop: observe, think, act, observe the result, and repeat until the task is complete.


Agent Loop

class Agent:
    def __init__(self, llm, tools, memory):
        self.llm = llm
        self.tools = tools
        self.memory = memory
    
    def run(self, task: str, max_steps: int = 10) -> str:
        """Main agent loop: think → act → observe → repeat."""
        self.memory.add("user", task)
        
        for step in range(max_steps):
            # THINK: LLM decides what to do next
            response = self.llm.generate(
                messages=self.memory.get_messages(),
                tools=self.tools.schemas(),
            )
            
            # CHECK: Is the agent done?
            if response.is_final_answer:
                return response.content
            
            # ACT: Execute the chosen tool
            tool_name = response.tool_call.name
            tool_args = response.tool_call.arguments
            
            try:
                result = self.tools.execute(tool_name, tool_args)
                self.memory.add("tool_result", {
                    "tool": tool_name,
                    "result": result,
                })
            except Exception as e:
                self.memory.add("tool_error", {
                    "tool": tool_name,
                    "error": str(e),
                })
        
        return "Max steps reached without completing task"

Tool Design

# Tools are the agent's interface to the world
class ToolRegistry:
    def __init__(self):
        self.tools = {}
    
    def register(self, name, func, description, parameters):
        self.tools[name] = Tool(
            name=name,
            func=func,
            description=description,
            parameters=parameters,
        )
    
    def schemas(self):
        """Return tool schemas for LLM function calling."""
        return [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description,
                    "parameters": tool.parameters,
                }
            }
            for tool in self.tools.values()
        ]

# Example tools
tools = ToolRegistry()

tools.register(
    name="search_database",
    func=search_db,
    description="Search the customer database by name, email, or ID",
    parameters={
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "Search query"},
            "field": {"type": "string", "enum": ["name", "email", "id"]},
        },
        "required": ["query", "field"],
    }
)

tools.register(
    name="send_email",
    func=send_email,
    description="Send an email to a customer",
    parameters={
        "type": "object",
        "properties": {
            "to": {"type": "string", "description": "Recipient email"},
            "subject": {"type": "string"},
            "body": {"type": "string"},
        },
        "required": ["to", "subject", "body"],
    }
)

Memory Systems

Short-Term Memory (Context Window):
  What: Current conversation and recent tool results
  Duration: Single session
  Limit: Context window size (128K tokens)
  
Long-Term Memory (Vector Store):
  What: Past interactions, learned facts, preferences
  Duration: Persistent across sessions
  Storage: Vector database (Pinecone, pgvector)
  
Working Memory (Scratchpad):
  What: Current plan, intermediate results, notes
  Duration: Current task
  Storage: Structured text in context

Guardrails

class AgentGuardrails:
    def pre_action_check(self, tool_name, args):
        """Validate before executing a tool."""
        
        # Destructive action check
        if tool_name in ["delete_record", "send_email", "modify_database"]:
            if not self.user_approval_received:
                return GuardrailResult.REQUIRE_APPROVAL
        
        # Rate limiting
        if self.action_count > self.max_actions_per_task:
            return GuardrailResult.STOP
        
        # Cost limiting
        if self.estimated_cost > self.max_cost:
            return GuardrailResult.STOP
        
        # PII check
        if self.contains_pii(args):
            return GuardrailResult.REDACT
        
        return GuardrailResult.ALLOW

Anti-Patterns

Anti-PatternConsequenceFix
No max step limitAgent loops forever, burns tokensHard limit on iterations
No guardrails on toolsAgent takes destructive actionsPre-action approval for dangerous tools
Too many toolsAgent confused, picks wrong tool5-10 focused tools per agent
No error recoverySingle tool failure stops agentError handling in loop, retry logic
No observabilityCannot debug agent behaviorLog every thought, tool call, and result

AI agents are powerful but unpredictable. The key to production agents is not making them smarter — it is making them safer, more observable, and more controllable.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →