Search by

hammrouni / nanoagent

hammrouni

NanoAgent is a lightweight, local-first PHP library that strips away the complexity of modern AI agent development — no Python sidecar, no framework. For Industry 4.0 / industrial IoT and edge contexts.

Package info

github.com/hammrouni/nanoagentphp

Homepage

Language:HTML

pkg:composer/hammrouni/nanoagent

Statistics

Installs: 27

Dependents: 0

Suggesters: 0

Stars: 8

Open Issues: 0

v0.5.0 2026-09-12 23:11 UTC

This package is auto-updated.

Last update: 2026-10-10 21:14:18 UTC


README

NanoAgent

NanoAgent PHP

CI Latest Version on Packagist Total Downloads License PHP Version GitHub Stars GitHub Issues

Add AI agents to your PHP app in an afternoon - local-first, no Python sidecar, no framework to learn.

composer require hammrouni/nanoagent  ·  PHP 8.0–8.4  ·  0 runtime dependencies  ·  MIT

📖 Full documentation & interactive examples

Why this exists

Most "AI agent" libraries assume you'll bolt a Python microservice behind your app, manage a fleet of config files, or adopt a whole framework. In existing PHP environments - where your service is already talking to legacy databases, internal APIs, and core business logic - that overhead is the wrong shape. You don't need a sidecar. You need a small, boring, local library you can composer require into the PHP you already run, hand it your own functions, and ship.

NanoAgent is that library. It is deliberately lightweight and local-first: one Composer package, zero runtime dependencies, no DSL, no framework lock-in, and memory that lives in your own files or database. It points at whichever provider you configure - a hosted API or a local model behind a compatible endpoint - so the agent can run right alongside your existing infrastructure.

What it does

  • ⚡ One config array, any provider. OpenAI, Groq, Anthropic, DeepSeek, OpenRouter - or any OpenAI-compatible endpoint, including a local model server. Swap in one line.
  • 🛠️ Tool-first architecture. Map plain PHP functions as tools the agent calls - your database, your APIs, your device telemetry.
  • 🔌 MCP support. Register any Model Context Protocol server (Streamable HTTP); its tools behave exactly like your local ones - and you can allowlist which ones to register.
  • 🧠 Persistent memory. Conversations survive across requests via pluggable drivers: files, SQLite, MySQL, PostgreSQL, or your own.
  • 💎 Minimal footprint. One package, no hidden magic, easy to audit.

🚀 Quick Start

Prerequisites

  • PHP 8.0 or higher (8.4 tested in CI)
  • Composer

1. Install

mkdir my-agent-project && cd my-agent-project
composer require hammrouni/nanoagent

2. Point it at a provider

A minimal agent - note it takes a plain config array, so a local model behind an OpenAI-compatible base_url works the same way as a hosted one:

<?php
use NanoAgent\Agent;

$agent = new Agent(llm: [
    'provider' => 'groq',
    'model'    => 'llama-3.3-70b-versatile',
    'api_key'  => getenv('GROQ_API_KEY'),
    // 'base_url' => 'http://localhost:8080/v1', // e.g. a local model server
]);

echo $agent->chat('Explain what NanoAgent does, in one sentence.');

Or load the array from a file (see NanoAgent/config.php.example):

$config = require __DIR__ . '/config.php';
$agent  = new Agent(llm: $config);

3. Give it a tool

use NanoAgent\Tools\FunctionTool;

$calculator = new FunctionTool(
    name: 'calculator',
    description: 'Add two numbers',
    parameters: [
        'type' => 'object',
        'properties' => [
            'a' => ['type' => 'integer', 'description' => 'First number'],
            'b' => ['type' => 'integer', 'description' => 'Second number'],
        ],
        'required' => ['a', 'b'],
    ],
    callable: fn(array $args) => $args['a'] + $args['b'],
);

$agent = new Agent(llm: [ /* ... */ ], tools: [$calculator]);
echo $agent->chat('What is 17 + 25?');

Want to run it fully locally? Point base_url at a local OpenAI-compatible server (e.g. http://localhost:8080/v1) with provider => 'openai'. The agent, your tools, and your memory all stay on the same host - no cloud round-trip.

🛠️ Tools & MCP

MCP: register every tool a Model Context Protocol server exposes in one call - they behave exactly like local tools from there on.

use NanoAgent\Mcp\McpClient;

$mcpClient = new McpClient('https://mcp.deepwiki.com/mcp');
$names = $agent->registerMcpServer($mcpClient);
echo $agent->chat('Ask the facebook/react repo what it does.');

Every registered MCP tool ships its full JSON Schema to the model on every turn, so a server with 50 tools costs 50 tool definitions per request. Pass an allowlist to register only the ones you need:

$names = $agent->registerMcpServer($mcpClient, allowlist: ['ask_question']);

Which names? Ask the server directly with McpClient::listTools() — it returns every tool it exposes, with descriptions, and registers nothing:

use NanoAgent\Mcp\McpClient;

$mcpClient = new McpClient('https://mcp.deepwiki.com/mcp');

foreach ($mcpClient->listTools() as $tool) {
    echo $tool['name'], "\n";                 // -> the name you put in the allowlist
    echo ($tool['description'] ?? ''), "\n";  // -> so you know what it's for
}

Each entry is ['name' => ..., 'description' => ..., 'inputSchema' => [...]]. Skip the allowlist on a first call to just register everything and read the names back:

$names = $agent->registerMcpServer($mcpClient);   // no allowlist -> registers all
print_r($names);                                  // then re-register narrowed

Keep the same $mcpClient object for both: the MCP handshake is memoized per client, so re-using it skips a re-initialize (the tools/list call itself is repeated). Note that there is no Agent::getTools() — the return value of registerMcpServer() is the only listing of what the agent currently holds.

Calling registerMcpServer() again on the same client reconciles rather than duplicates: tools you keep are untouched, tools you drop are removed from the agent, and locally registered tools are never affected. A name the server doesn't expose is ignored, not an error - handy when the allowlist comes from config.

🧠 Persistent Memory

PHP forgets everything between requests. Attach a memory driver and the agent loads the conversation for a session, then saves it after every completed chat() / stream() call.

use NanoAgent\Memory\FileMemory;

$agent = new Agent(llm: [ /* ... */ ]);
$agent->setMemory(new FileMemory(__DIR__ . '/storage/memory'), sessionId: "user-$userId");
echo $agent->chat($_POST['message']);
Driver Storage Requires
ArrayMemory Current process only (tests, workers) -
FileMemory One JSON file per session -
PdoMemory SQL table (SQLite, MySQL, PostgreSQL) ext-pdo + driver
use NanoAgent\Memory\PdoMemory;
$agent->setMemory(PdoMemory::sqlite(__DIR__ . '/memory.sqlite'), $sessionId);
$agent->setMemory(new PdoMemory($existingPdo), $sessionId); // creates `nanoagent_memory` if missing

See the PdoMemory guide for MySQL/PostgreSQL connections, table schemas and cleanup. Need another backend (Redis, a framework cache, Eloquent)? Implement NanoAgent\Contracts\Memory: load(), save(), clear().

📂 Examples

The examples/ directory has runnable demos (open examples/index.php in a browser or run any file with php):

Example Description
Basic Usage Agent init, a custom tool, and a task with context.
Chat Stateful conversation loop using PHP sessions.
Memory Chat Persistent history with a pluggable memory driver.
Structured Output Extract JSON from unstructured text.
Knowledge Base RAG: ask questions against private documents.
Streaming Real-time token streaming (SSE).
Agent Chain Multi-agent workflow (Researcher feeds Writer).
Multi-Tool Orchestrate several tools for one request.
MCP Tools Discover a remote MCP server's tools and allowlist which ones to register.
Multi-Provider Switch providers programmatically.
API Integration Fetch data from external APIs.
Advanced Tools Multi-step tool use with simulated DB state.

Supported Providers

Every provider below is covered by the automated test suite (see the CI badge). Groq has also been verified against the real API.

  • Groq
  • OpenAI
  • OpenRouter
  • DeepSeek
  • Anthropic - experimental. Built separately from the others, not yet verified against the real API, and streaming isn't fully wired up yet.
  • Any OpenAI-compatible endpoint - including a local model server, via base_url.

Changelog

0.6.0

  1. MCP allowlist: registerMcpServer($client, ['tool_a']) registers only the tools you name. Every registered tool ships its JSON Schema on every turn, so this is how you keep a large server's cost down. Unknown names are ignored, not an error.
  2. Calling registerMcpServer() again on the same client now reconciles instead of duplicating: tools dropped from the allowlist are removed from the agent, kept ones are untouched, and locally registered tools are never affected.

0.5.0

  1. Persistent memory: Agent::setMemory() keeps conversations across requests, with ArrayMemory, FileMemory and PdoMemory (SQLite, MySQL, PostgreSQL) drivers, or your own via NanoAgent\Contracts\Memory. See the PdoMemory guide.
  2. Clarified in the README that all providers are covered by the mocked unit test suite.

0.4.0

  1. MCP (Model Context Protocol) client support: connect to any Streamable HTTP MCP server and register its tools with registerMcpServer().

0.3.0

  1. Inject a custom provider directly into Agent, no config array needed.
  2. Configure temperature, max_tokens, and a custom base_url per provider.
  3. Streaming now supports tool calls.
  4. More reliable request encoding for non-English text.

0.2.0

  1. HTTP requests now have timeouts and surface real API errors instead of hanging or failing silently.
  2. Tool-calling loops are capped to avoid runaway API usage.
  3. Task context no longer leaks between calls.
  4. Added static analysis (PHPStan) and a wider PHP version test matrix (8.0–8.4).

0.1.0

First public release.

License

MIT

Author

Khaled Hammrouni - Technical PM & Software Architect, working in Industry 4.0 / industrial IoT with local-first AI tooling.