{"slug": "10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory", "title": "10 - Stateful Tools in PHP - An Agent That Reads and Mutates Inventory", "summary": "A developer demonstrated a stateful PHP agent that reads and mutates inventory, using NanoAgent's FunctionTool to wrap a ProductDatabase class with search_products and place_order tools. The agent queries stock, decides, and decrements inventory in a single turn, with a system prompt instructing it not to guess stock levels. After the run, the Quantum Laptop stock dropped from 5 to 3, confirming the mutation executed.", "body_md": "So far the tools have been read-only (weather, search). The more interesting case is a tool that **changes state** - placing an order, updating a record, firing an action. The agent has to *query* state, *decide*, then *mutate* it, all in one turn.\n\nThis example is an inventory agent: the user asks to buy something, the agent checks stock, then places the order.\n\nA plain PHP class stands in for a real database - an in-memory array of products, with methods to read and write it. This keeps the example runnable with no setup, while behaving exactly like a DB-backed store would.\n\n``` php\nclass ProductDatabase {\n    private array $products = [\n        'p1' => ['name' => 'Quantum Laptop', 'price' => 1500, 'stock' => 5],\n        'p2' => ['name' => 'Nano Phone',     'price' => 800,  'stock' => 0],\n        'p3' => ['name' => 'AI Headset',     'price' => 300,  'stock' => 12],\n    ];\n\n    public function search(string $query): array {\n        $out = [];\n        foreach ($this->products as $id => $p) {\n            if (stripos($p['name'], $query) !== false) $out[$id] = $p;\n        }\n        return $out;\n    }\n```\n\n`order()` is the mutating half: it validates the product exists and has enough stock *before* touching anything, then decrements the count and returns a human-readable result.\n\n```\n    public function order(string $id, int $qty): string {\n        if (!isset($this->products[$id])) return \"Error: Product not found.\";\n        if ($this->products[$id]['stock'] < $qty) return \"Error: Insufficient stock.\";\n        $this->products[$id]['stock'] -= $qty;   // <-- the mutation\n        return \"Success: Ordered $qty of {$this->products[$id]['name']}. \"\n             . \"New stock: {$this->products[$id]['stock']}\";\n    }\n\n    public function getInventory(): array { return $this->products; }\n}\n\n$db = new ProductDatabase();\n```\n\nThe read tool is a thin wrapper around `$db->search()` - no logic of its own, just a schema the model can call:\n\n``` php\nuse NanoAgent\\Agent;\nuse NanoAgent\\Tools\\FunctionTool;\n\n$searchTool = new FunctionTool(\n    name: 'search_products',\n    description: 'Search for products by name in the store inventory.',\n    parameters: [\n        'type' => 'object',\n        'properties' => ['query' => ['type' => 'string', 'description' => 'Product name or keyword']],\n        'required' => ['query'],\n        'additionalProperties' => false\n    ],\n    callable: fn(array $args) => $db->search($args['query'])\n);\n```\n\nThe write tool is the same shape, just pointed at `$db->order()` instead - the mutation itself lives entirely in the `ProductDatabase` class, not in the tool:\n\n``` php\n$orderTool = new FunctionTool(\n    name: 'place_order',\n    description: 'Place an order for a specific product by its ID.',\n    parameters: [\n        'type' => 'object',\n        'properties' => [\n            'product_id' => ['type' => 'string',  'description' => 'The unique product ID'],\n            'quantity'   => ['type' => 'integer', 'description' => 'Units to purchase']\n        ],\n        'required' => ['product_id', 'quantity'],\n        'additionalProperties' => false\n    ],\n    callable: fn(array $args) => $db->order($args['product_id'], $args['quantity'])\n);\nphp\n$agent = new Agent(\n    llm: $llmConfig,\n    systemPrompt: \"You are a retail sales assistant. You have tools 'search_products' and \"\n                . \"'place_order'. You MUST use these tools to check availability and place \"\n                . \"orders. Do not guess stock levels.\",\n    tools: [$searchTool, $orderTool]\n);\n$agent->enableActivityLogging();\n\n$response = $agent->chat(\"I want to buy 2 Quantum Laptops. Check stock and order.\");\necho $response;\n```\n\nCheck the database afterward and the stock is genuinely lower - this wasn't just a chat reply, the agent actually called `place_order` and it actually ran:\n\n``` php\nvar_dump($db->getInventory()['p1']['stock']);  // 5 -> 3\n```\n\nThe \"do not guess stock levels\" line in the prompt is load-bearing. Without it, a model might answer \"yes, we have some\" from memory. With it, the agent *must* call `search_products` to read the real number, then `place_order` to act on it.\n\n`\"Success: Ordered 2... New stock: 3\"` lets the model reason about the outcome and report it. A bare array gives it less to work with.`\"Error: Insufficient stock.\"` is returned to the model, which can then tell the user \"only 0 in stock\" instead of your app 500ing.`additionalProperties => false`` enableActivityLogging()`\nSwap `ProductDatabase` for real persistence and you've got an agent that can **read your DB, make a decision, and write back to your DB** - the core of most \"agentic\" business automation (orders, tickets, inventory, bookings). The agent is the decision layer; your PHP functions are the hands.\n\n*Part of the NanoAgent examples series. [Landing + demos](https://hammrouni.github.io/nanoagentphp/).*", "url": "https://wpnews.pro/news/10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory", "canonical_source": "https://dev.to/hammrouni/10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory-5e19", "published_at": "2026-09-27 20:51:39+00:00", "updated_at": "2026-09-27 21:00:50.212995+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": ["NanoAgent", "PHP", "ProductDatabase", "FunctionTool"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory", "markdown": "https://wpnews.pro/news/10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory.md", "text": "https://wpnews.pro/news/10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory.txt", "jsonld": "https://wpnews.pro/news/10-stateful-tools-in-php-an-agent-that-reads-and-mutates-inventory.jsonld"}}