{"id":890,"date":"2026-05-13T05:55:57","date_gmt":"2026-05-12T21:55:57","guid":{"rendered":"https:\/\/connectword.dpdns.org\/?p=890"},"modified":"2026-05-13T05:55:57","modified_gmt":"2026-05-12T21:55:57","slug":"build-a-hybrid-memory-autonomous-agent-with-modular-architecture-and-tool-dispatch-using-openai","status":"publish","type":"post","link":"https:\/\/connectword.dpdns.org\/?p=890","title":{"rendered":"Build a Hybrid-Memory Autonomous Agent with Modular Architecture and Tool Dispatch Using OpenAI"},"content":{"rendered":"<p>In this tutorial, we begin by exploring the architecture behind a hybrid-memory autonomous agent. This system combines semantic vector search, keyword-based retrieval, and a modular tool-dispatching loop to create an agent capable of reasoning, remembering, and acting autonomously. We walk through each layer of the design from the ground up, starting with abstract interfaces that enforce clean separation of concerns, all the way to a live agent that manages its own long-term memory.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">!pip install openai numpy rank_bm25 --quiet\n\n\nimport os, json, math, re, time, getpass\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass, field\nfrom typing import Any, Callable, Dict, List, Optional, Tuple\n\n\nimport numpy as np\nfrom rank_bm25 import BM25Okapi\nfrom openai import OpenAI\n\n\nOPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\") or getpass.getpass(\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f511.png\" alt=\"\ud83d\udd11\" class=\"wp-smiley\" \/>  Enter your OpenAI API key (hidden): \")\nclient = OpenAI(api_key=OPENAI_API_KEY)\n\n\nEMBED_MODEL = \"text-embedding-3-small\"\nCHAT_MODEL  = \"gpt-4o-mini\"\n\n\nprint(\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/>  OpenAI client ready.\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We kick things off by installing all required dependencies and configuring our Python environment with the necessary imports. We securely collect the OpenAI API key using getpass, ensuring the key is never echoed to the terminal or notebook output. We also define the two global constants, the embedding model and the chat model, that every subsequent snippet depends on.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">class MemoryBackend(ABC):\n   @abstractmethod\n   def store(self, text: str, metadata: Dict[str, Any]) -&gt; str: ...\n   @abstractmethod\n   def search(self, query: str, top_k: int = 5) -&gt; List[Dict[str, Any]]: ...\n   @abstractmethod\n   def list_all(self) -&gt; List[Dict[str, Any]]: ...\n\n\nclass LLMProvider(ABC):\n   @abstractmethod\n   def complete(self, messages: List[Dict], tools: Optional[List] = None) -&gt; Dict: ...\n\n\nclass Tool(ABC):\n   name: str\n   description: str\n\n\n   @abstractmethod\n   def run(self, **kwargs) -&gt; str: ...\n\n\n   def schema(self) -&gt; Dict:\n       return {\n           \"type\": \"function\",\n           \"function\": {\n               \"name\": self.name,\n               \"description\": self.description,\n               \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []},\n           },\n       }\n\n\n\n\n@dataclass\nclass MemoryChunk:\n   id: str\n   text: str\n   metadata: Dict[str, Any]\n   embedding: Optional[np.ndarray] = field(default=None, repr=False)\n\n\n\n\ndef _embed(texts: List[str]) -&gt; List[np.ndarray]:\n   resp = client.embeddings.create(model=EMBED_MODEL, input=texts)\n   vecs = [np.array(d.embedding, dtype=np.float32) for d in resp.data]\n   return [v \/ (np.linalg.norm(v) + 1e-10) for v in vecs]\n\n\n\n\ndef _tokenise(text: str) -&gt; List[str]:\n   return re.sub(r\"[^a-z0-9s]\", \"\", text.lower()).split()\n\n\n\n\nclass HybridMemory(MemoryBackend):\n   RRF_K = 60\n\n\n   def __init__(self):\n       self._chunks: List[MemoryChunk] = []\n       self._bm25: Optional[BM25Okapi] = None\n       self._counter = 0\n\n\n   def store(self, text: str, metadata: Dict[str, Any] | None = None) -&gt; str:\n       metadata = metadata or {}\n       self._counter += 1\n       chunk_id = f\"mem_{self._counter:04d}\"\n       [vec] = _embed([text])\n       chunk = MemoryChunk(id=chunk_id, text=text, metadata=metadata, embedding=vec)\n       self._chunks.append(chunk)\n       corpus = [_tokenise(c.text) for c in self._chunks]\n       self._bm25 = BM25Okapi(corpus)\n       print(f\"   <img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f4be.png\" alt=\"\ud83d\udcbe\" class=\"wp-smiley\" \/>  Stored [{chunk_id}]: {text[:60]}\u2026\" if len(text) &gt; 60 else f\"   <img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f4be.png\" alt=\"\ud83d\udcbe\" class=\"wp-smiley\" \/>  Stored [{chunk_id}]: {text}\")\n       return chunk_id\n\n\n   def search(self, query: str, top_k: int = 5) -&gt; List[Dict[str, Any]]:\n       if not self._chunks:\n           return []\n       n = len(self._chunks)\n       top_k = min(top_k, n)\n\n\n       [q_vec] = _embed([query])\n       cos_scores = np.array([np.dot(q_vec, c.embedding) for c in self._chunks])\n       vec_ranks = {self._chunks[i].id: rank + 1 for rank, i in enumerate(np.argsort(-cos_scores))}\n\n\n       bm25_scores = self._bm25.get_scores(_tokenise(query))\n       kw_ranks = {self._chunks[i].id: rank + 1 for rank, i in enumerate(np.argsort(-bm25_scores))}\n\n\n       rrf: Dict[str, float] = {}\n       for chunk in self._chunks:\n           cid = chunk.id\n           rrf[cid] = (1.0 \/ (self.RRF_K + vec_ranks.get(cid, n + 1)) +\n                       1.0 \/ (self.RRF_K + kw_ranks.get(cid, n + 1)))\n\n\n       ranked_ids = sorted(rrf, key=lambda x: rrf[x], reverse=True)[:top_k]\n       results = []\n       ids = [c.id for c in self._chunks]\n       for cid in ranked_ids:\n           chunk = next(c for c in self._chunks if c.id == cid)\n           results.append({\n               \"id\": chunk.id,\n               \"text\": chunk.text,\n               \"metadata\": chunk.metadata,\n               \"rrf_score\": round(rrf[cid], 6),\n               \"cosine\": round(float(cos_scores[ids.index(cid)]), 4),\n               \"bm25\": round(float(bm25_scores[ids.index(cid)]), 4),\n           })\n       return results\n\n\n   def list_all(self) -&gt; List[Dict[str, Any]]:\n       return [{\"id\": c.id, \"text\": c.text, \"metadata\": c.metadata} for c in self._chunks]\n\n\n\n\nclass OpenAIProvider(LLMProvider):\n   def __init__(self, model: str = CHAT_MODEL, temperature: float = 0.2):\n       self.model = model\n       self.temperature = temperature\n\n\n   def complete(self, messages: List[Dict], tools: Optional[List] = None) -&gt; Dict:\n       kwargs: Dict[str, Any] = dict(model=self.model, messages=messages, temperature=self.temperature)\n       if tools:\n           kwargs[\"tools\"] = tools\n           kwargs[\"tool_choice\"] = \"auto\"\n       response = client.chat.completions.create(**kwargs)\n       msg = response.choices[0].message\n       result: Dict[str, Any] = {\"role\": \"assistant\", \"content\": msg.content or \"\"}\n       if msg.tool_calls:\n           result[\"tool_calls\"] = [\n               {\n                   \"id\": tc.id,\n                   \"type\": \"function\",\n                   \"function\": {\"name\": tc.function.name, \"arguments\": tc.function.arguments},\n               }\n               for tc in msg.tool_calls\n           ]\n       return result\n\n\n\n\nprint(\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/>  Interfaces, HybridMemory, and OpenAIProvider ready.\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We define the three core abstract base classes, MemoryBackend, LLMProvider, and Tool, that serve as the interface contracts every concrete component must honour. We then implement HybridMemory, which stores embeddings for vector search and maintains a live BM25 index for keyword matching, merging both result sets using Reciprocal Rank Fusion. We close the snippet with OpenAIProvider, a concrete LLMProvider that normalises the OpenAI response into a provider-agnostic dictionary the agent can consume without knowing which model sits underneath.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">class MemoryStoreTool(Tool):\n   name = \"memory_store\"\n   description = \"Save an important fact or piece of information to long-term memory.\"\n\n\n   def __init__(self, memory: MemoryBackend):\n       self._mem = memory\n\n\n   def run(self, text: str, category: str = \"general\") -&gt; str:\n       chunk_id = self._mem.store(text, {\"category\": category})\n       return f\"Stored as {chunk_id}.\"\n\n\n   def schema(self) -&gt; Dict:\n       return {\n           \"type\": \"function\",\n           \"function\": {\n               \"name\": self.name,\n               \"description\": self.description,\n               \"parameters\": {\n                   \"type\": \"object\",\n                   \"properties\": {\n                       \"text\":     {\"type\": \"string\", \"description\": \"The fact to remember.\"},\n                       \"category\": {\"type\": \"string\", \"description\": \"Category tag, e.g. 'user_pref', 'task', 'fact'.\"},\n                   },\n                   \"required\": [\"text\"],\n               },\n           },\n       }\n\n\n\n\nclass MemorySearchTool(Tool):\n   name = \"memory_search\"\n   description = \"Search long-term memory for information relevant to a query.\"\n\n\n   def __init__(self, memory: MemoryBackend):\n       self._mem = memory\n\n\n   def run(self, query: str, top_k: int = 3) -&gt; str:\n       results = self._mem.search(query, top_k=top_k)\n       if not results:\n           return \"No relevant memories found.\"\n       lines = [f\"[{r['id']}] (score={r['rrf_score']}) {r['text']}\" for r in results]\n       return \"Relevant memories:n\" + \"n\".join(lines)\n\n\n   def schema(self) -&gt; Dict:\n       return {\n           \"type\": \"function\",\n           \"function\": {\n               \"name\": self.name,\n               \"description\": self.description,\n               \"parameters\": {\n                   \"type\": \"object\",\n                   \"properties\": {\n                       \"query\": {\"type\": \"string\", \"description\": \"What to look for.\"},\n                       \"top_k\": {\"type\": \"integer\", \"description\": \"Max results (default 3).\"},\n                   },\n                   \"required\": [\"query\"],\n               },\n           },\n       }\n\n\n\n\nclass CalculatorTool(Tool):\n   name = \"calculator\"\n   description = \"Evaluate a safe mathematical expression, e.g. '2 ** 10 + sqrt(144)'.\"\n\n\n   def run(self, expression: str) -&gt; str:\n       allowed = {k: getattr(math, k) for k in dir(math) if not k.startswith(\"_\")}\n       allowed.update({\"abs\": abs, \"round\": round})\n       try:\n           result = eval(expression, {\"__builtins__\": {}}, allowed)\n           return str(result)\n       except Exception as exc:\n           return f\"Error: {exc}\"\n\n\n   def schema(self) -&gt; Dict:\n       return {\n           \"type\": \"function\",\n           \"function\": {\n               \"name\": self.name,\n               \"description\": self.description,\n               \"parameters\": {\n                   \"type\": \"object\",\n                   \"properties\": {\n                       \"expression\": {\"type\": \"string\", \"description\": \"Math expression to evaluate.\"},\n                   },\n                   \"required\": [\"expression\"],\n               },\n           },\n       }\n\n\n\n\nclass WebSnippetTool(Tool):\n   name = \"web_search\"\n   description = \"Search the web for current information on a topic (simulated).\"\n\n\n   _KB = {\n       \"openai\": \"OpenAI is an AI safety company that develops the GPT family of models.\",\n       \"rag\": \"Retrieval-Augmented Generation (RAG) combines a retrieval system with an LLM to ground answers in external documents.\",\n       \"bm25\": \"BM25 (Best Match 25) is a probabilistic keyword ranking function used in search engines.\",\n   }\n\n\n   def run(self, query: str) -&gt; str:\n       q = query.lower()\n       for kw, snippet in self._KB.items():\n           if kw in q:\n               return f\"Web snippet for '{query}': {snippet}\"\n       return f\"No snippet found for '{query}'. (Mock tool \u2014 integrate a real search API here.)\"\n\n\n   def schema(self) -&gt; Dict:\n       return {\n           \"type\": \"function\",\n           \"function\": {\n               \"name\": self.name,\n               \"description\": self.description,\n               \"parameters\": {\n                   \"type\": \"object\",\n                   \"properties\": {\n                       \"query\": {\"type\": \"string\", \"description\": \"Search query.\"},\n                   },\n                   \"required\": [\"query\"],\n               },\n           },\n       }\n\n\n\n\n@dataclass\nclass AgentPersona:\n   name: str\n   role: str\n   traits: List[str]\n   forbidden_phrases: List[str] = field(default_factory=list)\n   goals: List[str] = field(default_factory=list)\n\n\n   def compile_system_prompt(self, extra_context: str = \"\") -&gt; str:\n       lines = [\n           f\"You are {self.name}, {self.role}.\",\n           \"\",\n           \"## Core Traits\",\n           *[f\"- {t}\" for t in self.traits],\n       ]\n       if self.goals:\n           lines += [\"\", \"## Goals\", *[f\"- {g}\" for g in self.goals]]\n       if self.forbidden_phrases:\n           lines += [\"\", \"## Forbidden Phrases (never say these)\", *[f\"- \"{p}\"\" for p in self.forbidden_phrases]]\n       if extra_context:\n           lines += [\"\", \"## Live Context\", extra_context]\n       lines += [\n           \"\",\n           \"## Behaviour\",\n           \"- Always reason step-by-step before answering.\",\n           \"- Use available tools proactively; never guess when you can look up.\",\n           \"- After using memory_search, quote the retrieved ID in your answer.\",\n           \"- Keep answers concise unless depth is explicitly requested.\",\n       ]\n       return \"n\".join(lines)\n\n\n\n\nARIA = AgentPersona(\n   name=\"Aria\",\n   role=\"a precise, helpful research assistant with a hybrid memory system\",\n   traits=[\"Methodical\", \"Curious\", \"Transparent about uncertainty\", \"Concise\"],\n   goals=[\n       \"Remember and connect information across conversations\",\n       \"Use tools whenever they can improve accuracy\",\n   ],\n   forbidden_phrases=[\"I cannot\", \"As an AI language model\"],\n)\n\n\nprint(\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/>  Tools and AgentPersona ready.\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We implement four tools, MemoryStoreTool, MemorySearchTool, CalculatorTool, and WebSnippetTool, each implementing the Tool interface and exposing an OpenAI-compatible JSON schema for automatic function invocation. We then introduce AgentPersona, a data class that compiles traits, goals, and forbidden phrases into a fully deterministic system prompt at runtime. We instantiate our demo persona, Aria, whose compiled prompt is injected at the top of every conversation turn to ensure consistent identity across all interactions.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">class AutonomousAgent:\n   MAX_TOOL_ROUNDS = 8\n\n\n   def __init__(self, persona: AgentPersona, llm: LLMProvider, memory: MemoryBackend, tools: List[Tool]):\n       self.persona  = persona\n       self._llm     = llm\n       self._memory  = memory\n       self._tools   = {t.name: t for t in tools}\n       self._history: List[Dict] = []\n\n\n   def chat(self, user_message: str, verbose: bool = True) -&gt; str:\n       if verbose:\n           print(f\"n{'\u2550'*60}\")\n           print(f\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f464.png\" alt=\"\ud83d\udc64\" class=\"wp-smiley\" \/>  USER: {user_message}\")\n           print(f\"{'\u2550'*60}\")\n\n\n       memory_context = self._build_memory_context(user_message)\n       system_prompt  = self.persona.compile_system_prompt(memory_context)\n\n\n       messages = [{\"role\": \"system\", \"content\": system_prompt}]\n       messages += self._history\n       messages.append({\"role\": \"user\", \"content\": user_message})\n\n\n       tool_schemas = [t.schema() for t in self._tools.values()]\n\n\n       for round_num in range(self.MAX_TOOL_ROUNDS):\n           reply = self._llm.complete(messages, tools=tool_schemas if tool_schemas else None)\n\n\n           if \"tool_calls\" not in reply:\n               final_text = reply[\"content\"]\n               if verbose:\n                   print(f\"n<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f916.png\" alt=\"\ud83e\udd16\" class=\"wp-smiley\" \/>  ARIA: {final_text}\")\n               self._history.append({\"role\": \"user\",      \"content\": user_message})\n               self._history.append({\"role\": \"assistant\", \"content\": final_text})\n               return final_text\n\n\n           messages.append(reply)\n\n\n           for tc in reply[\"tool_calls\"]:\n               tool_name = tc[\"function\"][\"name\"]\n               try:\n                   args = json.loads(tc[\"function\"][\"arguments\"])\n               except json.JSONDecodeError:\n                   args = {}\n\n\n               if verbose:\n                   print(f\"n<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f527.png\" alt=\"\ud83d\udd27\" class=\"wp-smiley\" \/>  TOOL CALL \u2192 {tool_name}({args})\")\n\n\n               result = self._tools[tool_name].run(**args) if tool_name in self._tools else f\"Error: unknown tool '{tool_name}'.\"\n\n\n               if verbose:\n                   print(f\"   \u21b3  RESULT: {result}\")\n\n\n               messages.append({\"role\": \"tool\", \"tool_call_id\": tc[\"id\"], \"content\": result})\n\n\n       return \"[Agent reached tool round limit \u2014 please rephrase your request.]\"\n\n\n   def register_tool(self, tool: Tool) -&gt; None:\n       self._tools[tool.name] = tool\n       print(f\"   <img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f50c.png\" alt=\"\ud83d\udd0c\" class=\"wp-smiley\" \/>  Tool registered: {tool.name}\")\n\n\n   def list_tools(self) -&gt; List[str]:\n       return list(self._tools.keys())\n\n\n   def memory_dump(self) -&gt; List[Dict]:\n       return self._memory.list_all()\n\n\n   def clear_history(self) -&gt; None:\n       self._history.clear()\n\n\n   def _build_memory_context(self, query: str) -&gt; str:\n       results = self._memory.search(query, top_k=3)\n       if not results:\n           return \"\"\n       snippets = \"n\".join(f\"- [{r['id']}] {r['text']}\" for r in results)\n       return f\"Recalled memories related to this query:n{snippets}\"\n\n\n\n\nmemory = HybridMemory()\nllm    = OpenAIProvider(model=CHAT_MODEL)\ntools  = [\n   MemoryStoreTool(memory),\n   MemorySearchTool(memory),\n   CalculatorTool(),\n   WebSnippetTool(),\n]\n\n\nagent = AutonomousAgent(persona=ARIA, llm=llm, memory=memory, tools=tools)\n\n\nprint(f\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/>  Agent '{ARIA.name}' bootstrapped with tools: {agent.list_tools()}\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We build the AutonomousAgent class, which owns the agentic loop, repeatedly sending messages to the LLM, detecting tool calls, dispatching them to the correct tool, and feeding results back until a plain-text reply is produced. We wire together all prior components, HybridMemory, OpenAIProvider, the four tools, and the Aria persona, into a single bootstrapped agent instance ready to receive user messages. We also expose utility methods, such as register_tool for runtime hot-swapping and memory_dump for inspecting the full state of long-term memory.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">print(\"n\" + \"\u2550\"*60)\nprint(\"DEMO 1 \u2014 Pre-seeding long-term memory\")\nprint(\"\u2550\"*60)\n\n\nfacts = [\n   (\"Alice's favourite programming language is Rust.\", {\"category\": \"user_pref\"}),\n   (\"Alice is working on a distributed key-value store called 'VelocityDB'.\", {\"category\": \"task\"}),\n   (\"VelocityDB uses the Raft consensus algorithm for replication.\", {\"category\": \"fact\"}),\n   (\"Alice has a meeting with the infrastructure team on Friday at 2 PM.\", {\"category\": \"calendar\"}),\n   (\"The project deadline for VelocityDB v1.0 is March 31.\", {\"category\": \"task\"}),\n   (\"Alice prefers concise answers without unnecessary preamble.\", {\"category\": \"user_pref\"}),\n   (\"Order #4821 was placed by Alice for 32 GB of DDR5 RAM modules.\", {\"category\": \"order\"}),\n]\n\n\nfor text, meta in facts:\n   memory.store(text, meta)\n\n\n\n\nprint(\"n\" + \"\u2550\"*60)\nprint(\"DEMO 2 \u2014 Hybrid Memory Search Showdown\")\nprint(\"\u2550\"*60)\n\n\ntest_queries = [\n   \"What consensus algorithm does VelocityDB use?\",\n   \"order 4821\",\n   \"Alice's language preference\",\n]\n\n\nfor q in test_queries:\n   print(f\"n<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f50d.png\" alt=\"\ud83d\udd0d\" class=\"wp-smiley\" \/>  Query: '{q}'\")\n   results = memory.search(q, top_k=2)\n   for r in results:\n       print(f\"   [{r['id']}] cosine={r['cosine']:.3f}  bm25={r['bm25']:.2f}  rrf={r['rrf_score']:.5f}\")\n       print(f\"        \u2192 {r['text']}\")\n\n\n\n\nprint(\"n\" + \"\u2550\"*60)\nprint(\"DEMO 3 \u2014 Autonomous Agent Conversations\")\nprint(\"\u2550\"*60)\n\n\nagent.chat(\"What do you know about Alice's project? What's the deadline and which algorithm does it rely on?\")\nagent.chat(\"Can you find the details on order number 4821?\")\nagent.chat(\n   \"There are 22 working days until March 31. \"\n   \"If Alice works 6.5 hours per day on VelocityDB, \"\n   \"how many total hours does she have left?\"\n)\nagent.chat(\n   \"Alice just decided to switch the storage engine of VelocityDB from LSM-tree to B-tree. \"\n   \"Please remember this decision.\"\n)\nagent.chat(\"What storage engine decision did Alice make for VelocityDB?\")\n\n\n\n\nprint(\"n\" + \"\u2550\"*60)\nprint(\"DEMO 4 \u2014 Runtime Tool Hot-Swap (vtable pattern)\")\nprint(\"\u2550\"*60)\n\n\nclass UpgradedWebSnippetTool(WebSnippetTool):\n   _KB = {\n       **WebSnippetTool._KB,\n       \"lsm-tree\": \"An LSM-tree (Log-Structured Merge-tree) optimises write throughput at the cost of read amplification.\",\n   }\n\n\nagent.register_tool(UpgradedWebSnippetTool())\nagent.chat(\n   \"Can you search the web for a brief explanation of B-tree storage engines \"\n   \"and tell me if it's a good match for Alice's project?\"\n)\n\n\n\n\nprint(\"n\" + \"\u2550\"*60)\nprint(\"FINAL \u2014 Full Memory Dump\")\nprint(\"\u2550\"*60)\n\n\nfor chunk in agent.memory_dump():\n   print(f\"  [{chunk['id']}] ({chunk['metadata'].get('category','?')}) {chunk['text']}\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We run four progressive demo scenarios that exercise every layer of the architecture we have built: seeding long-term memory with structured facts, running direct hybrid search queries to observe how vector and BM25 scores combine, conducting a multi-turn autonomous conversation where the agent recalls, computes, and stores information on its own, and finally hot-swapping a tool at runtime to demonstrate the vtable pattern in action. We close by dumping the full memory state to verify that all autonomously stored decisions have been correctly persisted. We end with an architecture recap table that maps every component back to the pattern it implements.<\/p>\n<p>In conclusion, we have walked through the complete construction of a hybrid-memory autonomous agent, from abstract interface contracts and dual-path retrieval all the way to a self-directing agent loop that stores, recalls, and reasons over information without any hard-coded logic. We have seen how the modular design allows any component, the memory backend, the language model provider, or individual tools,\u00a0 to be swapped or extended at runtime with zero changes to the agent core. This property makes the architecture genuinely production-ready.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<p>Check out\u00a0the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Agents-Projects-Tutorials\/blob\/main\/AI%20Agents%20Codes\/hybrid_memory_autonomous_agent_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">Full Codes with Notebook here<\/a>.\u00a0<\/strong>Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noreferrer noopener\">150k+ ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/www.aidevsignals.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noreferrer noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p>Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.?\u00a0<strong><a href=\"https:\/\/forms.gle\/MTNLpmJtsFA3VRVd9\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Connect with us<\/mark><\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2026\/05\/12\/build-a-hybrid-memory-autonomous-agent-with-modular-architecture-and-tool-dispatch-using-openai\/\">Build a Hybrid-Memory Autonomous Agent with Modular Architecture and Tool Dispatch Using OpenAI<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we begin by &hellip;<\/p>\n","protected":false},"author":1,"featured_media":29,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-890","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts\/890","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=890"}],"version-history":[{"count":0,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts\/890\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/media\/29"}],"wp:attachment":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=890"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=890"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=890"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}