Agentic AI Fundamentals: Planning, Tools, Memory, and Control Loops

Agentic AI systems do more than generate one response. They decompose goals, choose actions, call tools, inspect outcomes, and iterate until completion or stop condition.

This article explains the architecture patterns that make agents useful and safe.


What Makes a System “Agentic”

An agentic system usually has:

  • a goal
  • an action loop
  • tool access
  • state/memory
  • a stopping policy

Without these, most systems are still prompt-response assistants.


Core Control Loop

A practical loop:

  1. interpret user goal
  2. plan next step
  3. select tool or reasoning action
  4. execute
  5. observe result
  6. update state
  7. repeat until done/failed/timeout

This is often called a perceive-plan-act loop.


Planning Strategies

Common options:

  • reactive planning (decide step-by-step)
  • upfront plan then execute
  • hybrid (high-level upfront, dynamic re-plan)

Reactive is flexible but can drift. Upfront plans are traceable but brittle under unexpected tool outputs. Hybrid is usually best in production.


Tool Use Patterns

Agents gain capability through tools:

  • search/retrieval
  • calculators/code execution
  • APIs (CRM, billing, ticketing)
  • workflow actions (create ticket, send email)

Tool interfaces should be schema-constrained. Free-form tool arguments create reliability and security issues.


Memory Design

Agent memory types:

  • short-term: session context and recent tool outputs
  • long-term: user preferences, historical outcomes
  • external memory: vector retrieval / knowledge store

Memory policies need TTL and access controls. Unbounded memory can cause prompt bloat and privacy risk.


Termination and Guardrails

Agents need explicit stop conditions:

  • goal completed
  • confidence below threshold
  • max steps reached
  • repeated tool failure
  • policy violation detected

Without stop conditions, agents can loop, hallucinate actions, or create costly tool churn.


Failure Modes

Common failures:

  1. plan drift (agent chases irrelevant subgoals)
  2. tool misuse (wrong API, bad params)
  3. stale memory causing wrong decisions
  4. infinite loops
  5. unsafe action execution

Strong observability is required: step logs, tool traces, and decision audit trail.


Architecture Blueprint

A production agent stack usually includes:

  • orchestrator/runtime
  • planner policy
  • tool registry and permission layer
  • memory manager
  • policy and safety filters
  • evaluator/monitoring

Treat each layer as explicit component with ownership.


End-to-End Code Example (Minimal Agent Loop)

from dataclasses import dataclass, field
from typing import Dict, Any, List

MAX_STEPS = 6

@dataclass
class AgentState:
    goal: str
    steps: List[str] = field(default_factory=list)
    context: Dict[str, Any] = field(default_factory=dict)
    done: bool = False

# Tool registry

def tool_search_docs(query: str) -> Dict[str, Any]:
    # Replace with real retrieval
    return {"docs": [f"Result for: {query}"]}

def tool_calculator(expr: str) -> Dict[str, Any]:
    return {"value": eval(expr)}

TOOLS = {
    "search_docs": tool_search_docs,
    "calculator": tool_calculator,
}

def planner(state: AgentState) -> Dict[str, Any]:
    # Replace this with LLM planner
    if "calculate" in state.goal.lower() and "calc_done" not in state.context:
        return {"action": "calculator", "args": {"expr": "(1200*1.18)-200"}}
    if "docs_done" not in state.context:
        return {"action": "search_docs", "args": {"query": state.goal}}
    return {"action": "finish", "args": {"answer": "Task completed with available tools."}}

def run_agent(goal: str) -> AgentState:
    state = AgentState(goal=goal)

    for step_id in range(1, MAX_STEPS + 1):
        decision = planner(state)
        action = decision["action"]
        args = decision.get("args", {})
        state.steps.append(f"step={step_id} action={action} args={args}")

        if action == "finish":
            state.context["final_answer"] = args.get("answer", "Done")
            state.done = True
            break

        if action not in TOOLS:
            state.context["error"] = f"Unknown tool: {action}"
            break

        try:
            result = TOOLS[action](**args)
            state.context[f"tool_result_{step_id}"] = result
            if action == "search_docs":
                state.context["docs_done"] = True
            if action == "calculator":
                state.context["calc_done"] = True
        except Exception as e:
            state.context["error"] = str(e)
            break

    if not state.done and "error" not in state.context:
        state.context["error"] = "Stopped: max steps reached"

    return state

if __name__ == "__main__":
    s = run_agent("Calculate adjusted invoice and find policy docs")
    print("DONE:", s.done)
    print("STEPS:")
    for x in s.steps:
        print(" -", x)
    print("CONTEXT:", s.context)

This skeleton is intentionally simple; production agents need policy checks and robust tool contracts.


Key Takeaways

  • Agentic AI requires looped planning, action, and state updates.
  • Tools and memory increase capability but also operational risk.
  • Termination conditions and guardrails are mandatory.
  • Observability and traceability are essential for debugging and trust.