Most AI agent tutorials assume you are starting from a modern stack: Python, Docker, Redis, background workers, vector databases, and cloud infrastructure already built for orchestration. That is useful if you are working inside a mature platform team. But a lot of real software still runs on plain PHP, MySQL, and cPanel, and many builders still need to ship useful AI features without first rebuilding their entire infrastructure.That was the challenge behind this project.I wanted to build an AI agent that could: accept a user request, decide whether it needed a tool, execute that tool in PHP, store conversation state in MySQL, and keep reasoning until it produced a final answer. In this article, I will walk through how I built that system using: PHP for orchestration MySQL for memory Gemini Flash for reasoning and function calling cPanel for deployment on standard shared hosting The point is not to show off a fancy demo. The point is to show that a production-ready AI agent can be practical, understandable, and cheap to run.By the end, you will understand: how the agent loop works how function calling connects an LLM to real tools how to persist memory without overengineering how to deploy the whole thing on hosting most developers already know how to use The Real Problem With Most AI Agent Tutorials Most tutorials stop at the fun part. They show you how to send a prompt to an LLM and maybe how to call one tool once. That is enough to prove the concept, but it is not enough to build something that actually behaves like an agent in the real world.A real agent needs more than a clever prompt.It needs to: understand when it should use a tool pass structured arguments to that tool handle the tool output continue the conversation remember what happened before That is where a lot of examples fall apart.The system I built focuses on that missing middle layer, the orchestration between model, tools, and memory.Why This Stack Works The reason I chose PHP, MySQL, Gemini Flash, and cPanel is simple: this stack is familiar, accessible, and good enough for a surprising number of production use cases. Here is what each piece does: PHP handles HTTP requests and the agent loop MySQL stores chat memory and saved data Gemini Flash decides whether the agent should answer directly or use a tool cPanel makes deployment possible on standard shared hosting The important idea is that the agent is not a monolith. It is a loop: receive user input send context to the model let the model request a tool if needed execute the tool send the result back repeat until the model produces a final answer That pattern is small, but it scales surprisingly well.Architecture Overview At a high level, the system looks like this: [User Chat] -> [PHP Endpoint] -> [Gemini Flash] ^ | | | | v +------- [MySQL Memory] PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); } return $pdo; } This file exists for one reason: to make every database call use the same connection logic.That might look basic, but basic is good here. Complexity should live in the agent behavior, not in the database bootstrap code.Call Gemini Flash from PHP The model wrapper should handle the API call and normalize the response into something the agent loop can reason about. gemini.php: $contents ]; if (!empty($tools)) { $payload["tools"] = [ [ "functionDeclarations" => $tools ] ]; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json" ], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_TIMEOUT => 30 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { throw new RuntimeException("Gemini API error: " . $response); } return json_decode($response, true); } function parse_gemini_response(array $response): array { $part = $response["candidates"][0]["content"]["parts"][0] ?? []; if (isset($part["functionCall"])) { return [ "type" => "function_call", "name" => $part["functionCall"]["name"], "args" => $part["functionCall"]["args"] ?? [] ]; } return [ "type" => "text", "text" => $part["text"] ?? "" ]; } This wrapper does two jobs: Sends the model the conversation and tool definitions Converts the result into either plain text or a function call That keeps the agent loop clean and predictable.Define the Tool Registry The model should not be able to call arbitrary functions. It should only know about the tools you explicitly allow. tool_registry.php: "save_note", "description" => "Save an important note to the database.", "parameters" => [ "type" => "object", "properties" => [ "note" => [ "type" => "string" ] ], "required" => ["note"] ] ], [ "name" => "search_web", "description" => "Search the web for current information.", "parameters" => [ "type" => "object", "properties" => [ "query" => [ "type" => "string" ] ], "required" => ["query"] ] ], [ "name" => "send_email", "description" => "Send an email when the user explicitly asks.", "parameters" => [ "type" => "object", "properties" => [ "to" => ["type" => "string"], "subject" => ["type" => "string"], "body" => ["type" => "string"] ], "required" => ["to", "subject", "body"] ] ] ]; } This is the agent’s allowed action set.That constraint is important. The model can request actions, but it cannot invent new capabilities on its own.Build the Tools Now comes the part where the model’s intent becomes a real action. Save Note Tooltools/save_note.php: false, "message" => "Empty note"]); } $stmt = db()->prepare( "INSERT INTO agent_notes (session_id, note) VALUES (:session_id, :note)" ); $stmt->execute([ ":session_id" => $sessionId, ":note" => $note ]); return json_encode(["success" => true, "message" => "Note saved"]); } This tool demonstrates the basic production pattern: validate input write to the database return a structured response Search Web Tooltools/search_web.php: true, CURLOPT_TIMEOUT => 15 ]); $response = curl_exec($ch); curl_close($ch); return $response ?: json_encode(["error" => "Search failed"]); } In a real build, you would swap the placeholder URL for an actual search API.The pattern is what matters: receive structured arguments call an external service return the result to the agent Send Email Tooltools/send_email.php: false, "message" => "Invalid email"]); } $headers = "From: agent@yourdomain.com\r\n"; $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; $sent = mail($to, $subject, $body, $headers); return json_encode([ "success" => $sent, "message" => $sent ? "Email sent" : "Email failed" ]); } Again, the shape is the same: validate act return machine-readable output That consistency makes the agent easier to extend later.Add MySQL Conversation Memory Memory is what lets the agent behave like a conversation instead of a one-off request. memory.php:prepare( "SELECT role, content FROM agent_memory WHERE session_id = :session_id ORDER BY id ASC" ); $stmt->execute([":session_id" => $sessionId]); $history = []; foreach ($stmt->fetchAll() as $row) { $history[] = [ "role" => $row["role"], "parts" => [ ["text" => $row["content"]] ] ]; } return $history; } function save_memory(string $sessionId, array $history): void { $pdo = db(); $pdo->prepare("DELETE FROM agent_memory WHERE session_id = :session_id") ->execute([":session_id" => $sessionId]); $stmt = $pdo->prepare( "INSERT INTO agent_memory (session_id, role, content) VALUES (:session_id, :role, :content)" ); foreach ($history as $turn) { $text = $turn["parts"][0]["text"] ?? json_encode($turn["parts"][0]); $stmt->execute([ ":session_id" => $sessionId, ":role" => $turn["role"], ":content" => $text ]); } } This is a pragmatic approach for a first version.It is not the most sophisticated memory system possible, but it is simple, durable, and easy to understand.Create the Agent Loop This is the core of the entire system. The agent loop is where the conversation becomes more than a single API call. It lets the model decide when to use tools, then returns the result to the model until it produces a final response.agent.php: save_note_tool($args, $sessionId), "search_web" => search_web_tool($args), "send_email" => send_email_tool($args), default => json_encode(["success" => false, "message" => "Unknown tool"]) }; } function run_agent(string $message, string $sessionId): string { $history = load_memory($sessionId); $history[] = [ "role" => "user", "parts" => [ ["text" => $message] ] ]; $tools = tool_definitions(); $limit = 5; $step = 0; while ($step "model", "parts" => [ ["text" => $parsed["text"]] ] ]; save_memory($sessionId, $history); return $parsed["text"]; } if ($parsed["type"] === "function_call") { $toolName = $parsed["name"]; $toolArgs = $parsed["args"]; $result = run_tool($toolName, $toolArgs, $sessionId); $history[] = [ "role" => "model", "parts" => [ [ "functionCall" => [ "name" => $toolName, "args" => $toolArgs ] ] ] ]; $history[] = [ "role" => "function", "parts" => [ [ "functionResponse" => [ "name" => $toolName, "response" => [ "content" => $result ] ] ] ] ]; } } save_memory($sessionId, $history); return "I could not complete the task within the allowed number of steps."; } This is the real architecture lesson.The model is not doing everything. It is deciding what should happen next. PHP executes the action. The tool result goes back into the loop. Then the model keeps going.That is how you turn a language model into an agent.Expose the Public API Endpoint Now we need one public entry point that receives user input and returns JSON. index.php: "message and session_id are required"]); exit; } try { $reply = run_agent($message, $sessionId); echo json_encode([ "reply" => $reply, "session_id" => $sessionId ]); } catch (Throwable $e) { http_response_code(500); echo json_encode([ "error" => $e->getMessage() ]); } This endpoint stays small on purpose.Its job is not to reason. Its job is to validate the request, call the agent, and return the response.That is clean API design.Deploy on cPanel One of the main reasons this stack is useful is that it can be deployed on infrastructure many teams already have. Deployment steps: Upload the files to /public_html/agent/ Create the database and user in cPanel Grant the user access to the database Update db.php with the real MySQL credentials Put your Gemini API key into gemini.php Make sure PHP 8.1 or newer is selected Make sure cURL is enabled Protect the tools folder with .htaccess Once that is done, your endpoint should be live at something like:https://yourdomain.com/agent/index.php The nice part here is that the stack does not require special hosting assumptions. It runs in a very common web environment.Test the Agent Before calling this production-ready, test the three things that matter most: request handling tool execution memory persistence Save a notecurl -X POST https://yourdomain.com/agent/index.php \ -H "Content-Type: application/json" \ -d '{"message":"Save a note that our launch is on 1 September 2026","session_id":"demo123"}' Search the webcurl -X POST https://yourdomain.com/agent/index.php \ -H "Content-Type: application/json" \ -d '{"message":"Search the web for current Gemini Flash information","session_id":"demo123"}' Test memorycurl -X POST https://yourdomain.com/agent/index.php \ -H "Content-Type: application/json" \ -d '{"message":"What note did I save earlier?","session_id":"demo123"}' If that last call works, then the memory layer is doing its job.What This Architecture Teaches Us The biggest takeaway from this project is not that PHP can call an LLM. It is that production-ready AI does not have to be overengineered.A few practical lessons stand out: A simple stack can still be powerful. You do not need heavyweight cloud infrastructure to ship useful AI features. Function calling is the key bridge. The model becomes much more useful when it can safely trigger real tools. Memory changes everything. Stateless prompts are not enough if you want continuity. Clear boundaries make the system safer. The model reasons, PHP executes, and MySQL persists. Practical beats trendy. A system that is easy to deploy and maintain is often more valuable than one built on fashionable infrastructure. That is why this architecture is interesting. It is not trying to impress you with complexity. It is trying to solve the actual problem.Conclusion You do not need a giant cloud stack to build a useful AI agent. With PHP, MySQL, Gemini Flash, and cPanel, you can build a system that reasons, calls tools, stores memory, and runs on infrastructure that is cheap, familiar, and already available to many developers.That makes this approach especially useful for founders, indie hackers, and small teams that want to ship real AI functionality without turning their backend into a science project.The core lesson is simple: production-ready AI does not have to be complicated. It just has to be well structured.
How I Built a Production-Ready AI Agent on PHP, cPanel, and Gemini Flash
Full Article
Original Source
Read the full article at Hackernoon →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.