An embeddable AI assistant framework for PHP built on top of Neuron AI. Provides advanced context management, sub-agent orchestration, skill configuration, auto-learning, and MCP integration.
- Context Condensation - 4 strategies for intelligent context reduction before delegation
- Sub-Agent Orchestration - Delegate tasks to specialized agents with automatically condensed context
- Skill System - Configure reusable instruction modules via Markdown files with YAML frontmatter
- File Reading - Built-in tool for reading PDF, DOCX, TXT, CSV, Markdown, and more
- Auto-Learning - Record tool patterns, collect bugs, and get intelligent suggestions (with anti-poisoning guardrails)
- User Memory - Per-user persistent memories scoped by backend-provided user ID
- MCP Integration - Native support for stdio, SSE, and HTTP transports via Neuron's MCP connector
- Security First - Encrypted config, sensitive data redaction, MCP command allowlist, PSR-3 logging
- Structured Output - Typed PHP objects with validation via Neuron's
#[SchemaProperty]system
- PHP 8.2+
- Neuron AI ^3.0
composer require hacklab/ai-assistantuse HackLab\AIAssistant\Assistant;
use HackLab\AIAssistant\AssistantConfig;
use HackLab\AIAssistant\Persistence\FileStorage;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\Anthropic\Anthropic;
$assistant = Assistant::configure(
new AssistantConfig(
provider: new Anthropic(
key: 'your-api-key',
model: 'claude-sonnet-4',
),
storage: new FileStorage(__DIR__ . '/storage'),
instructions: 'You are a helpful coding assistant.',
)
);
$response = $assistant->chat(new UserMessage('Hello!'));
echo $response->getMessage()->getContent();use HackLab\AIAssistant\AssistantConfig;
use HackLab\AIAssistant\Persistence\FileStorage;
$config = new AssistantConfig(
provider: $aiProvider, // Neuron AIProviderInterface (required)
storage: new FileStorage('/path/to/storage'), // StorageInterface (required)
instructions: 'System prompt', // Base instructions
contextWindow: 200000, // Token limit (default: 200000)
tools: [], // Array of Tool classes/instances
subAgents: [], // Sub-agent configurations
skills: [], // Skill names to load
skillsPath: '/path/to/skills', // Directory containing .md skill files
autoLearn: false, // Enable auto-learning
autoDelegate: true, // Enable auto-delegation to sub-agents
requireLearningCheck: true, // Mandatory learning check before using tools
userId: $currentUser->getId(), // Backend-provided user ID (for user memory)
logger: $psr3Logger, // PSR-3 logger (optional)
requestTimeout: 120.0, // HTTP client timeout in seconds (null = provider default: 60s)
outputClass: null, // FQCN for structured output (class with #[SchemaProperty])
structuredMaxRetries: 1, // Retries on structured output validation failure
conversationStorage: null, // Override storage for conversations (falls back to $storage)
learningStorage: null, // Override storage for learning data (falls back to $storage)
userMemoryStorage: null, // Override storage for user memories (falls back to $storage)
);Assign different backends for conversations, learning, and user memories:
new AssistantConfig(
provider: $provider,
storage: new FileStorage('/data/default'),
conversationStorage: new RedisStorage($redis), // conversations in Redis
learningStorage: new FileStorage('/data/learning'), // learning on disk
userMemoryStorage: new DatabaseStorage($pdo), // memories in DB
);Implement StorageInterface for custom backends (database, Redis, etc.):
use HackLab\AIAssistant\Persistence\StorageInterface;
class RedisStorage implements StorageInterface {
public function save(string $namespace, string $key, array $data): void { /* ... */ }
public function load(string $namespace, string $key): ?array { /* ... */ }
public function delete(string $namespace, string $key): bool { /* ... */ }
public function exists(string $namespace, string $key): bool { /* ... */ }
public function list(string $namespace, string $pattern = '*'): array { /* ... */ }
public function search(string $namespace, string $query, int $limit = 10): array { /* ... */ }
public function cleanup(string $namespace, array $criteria = []): int { /* ... */ }
}
new AssistantConfig(
provider: $provider,
storage: new RedisStorage($redis),
)Any Neuron AI provider can be used:
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\OpenAI\OpenAI;
use NeuronAI\Providers\Gemini\Gemini;
use NeuronAI\Providers\Ollama\Ollama;
use NeuronAI\Providers\Deepseek\Deepseek;
// Anthropic Claude
new Anthropic(key: 'sk-ant-...', model: 'claude-sonnet-4');
// OpenAI GPT
new OpenAI(key: 'sk-...', model: 'gpt-4o');
// Google Gemini
new Gemini(key: '...', model: 'gemini-2.0-flash');
// Local Ollama (no API key needed)
new Ollama(model: 'llama3.2');
// Deepseek
new Deepseek(key: '...', model: 'deepseek-chat');Use OpenAILike for any provider with an OpenAI-compatible API:
use NeuronAI\Providers\OpenAILike;
// Z.AI Coding Plan
new OpenAILike(
baseUri: 'https://api.z.ai/api/coding/paas/v4',
key: 'your-zai-api-key',
model: 'glm-5.1',
parameters: [],
strict_response: false,
httpClient: null
);
// Any other OpenAI-compatible provider
new OpenAILike(
baseUri: 'https://api.custom-provider.com/v1',
key: 'your-key',
model: 'your-model',
parameters: [],
strict_response: false,
httpClient: null
);Delegate tasks to specialized agents with automatically condensed context:
use HackLab\AIAssistant\SubAgents\SubAgentConfig;
$assistant = Assistant::configure(
new AssistantConfig(
provider: new Anthropic('key', 'claude-sonnet-4'),
storage: new FileStorage(__DIR__ . '/storage'),
subAgents: [
'code-reviewer' => new SubAgentConfig(
id: 'code-reviewer',
provider: new OpenAI('key', 'gpt-4'),
instructions: 'You are an expert code reviewer...',
tools: [GitToolkit::class, FileSystemToolkit::class],
skills: ['security', 'psr12'],
contextStrategy: 'code-focused',
contextWindow: 8000,
mcps: [
['type' => 'stdio', 'command' => 'npx', 'args' => ['@modelcontextprotocol/server-github']],
],
),
],
)
);
// Delegate to sub-agent
$result = $assistant->delegate('code-reviewer', new UserMessage('Check for security issues'));
echo $result->getContent();When delegating to sub-agents, context is automatically condensed using one of 4 strategies:
| Strategy | Description | Best For |
|---|---|---|
| Truncation | Simple token-based cutting | Emergency fallback |
| Summarization | LLM-powered summarization | Long conversations |
| Relevance | Keyword/pattern matching | Task-specific delegation |
| Hierarchical | Summary + recent + key facts | Complex multi-turn (default) |
Configure per sub-agent:
new SubAgentConfig(
// ...
contextStrategy: 'code-focused', // Use relevance strategy with code keywords
)Skills are reusable instruction modules stored as Markdown files with YAML frontmatter:
---
name: Security Auditor
description: OWASP security specialist
tools:
- StaticAnalysisTool
- DependencyCheckTool
context_strategy: security-focused
---
When reviewing code:
- Check for SQL injection, XSS, CSRF
- Validate prepared statements
- Never expose secretsLoad skills from a directory:
new AssistantConfig(
provider: $provider,
storage: $storage,
skillsPath: __DIR__ . '/skills',
skills: ['security'], // Reference by name
)Connect to MCP servers via stdio, SSE, or HTTP:
// stdio (local process)
['type' => 'stdio', 'command' => 'npx', 'args' => ['@modelcontextprotocol/server-github']]
// SSE (Server-Sent Events)
['type' => 'sse', 'url' => 'http://localhost:8080/sse', 'token' => 'optional-bearer-token']
// HTTP Streaming
['type' => 'http', 'url' => 'https://api.example.com/mcp']Enable to record tool usage patterns, collect bugs, and build contextual knowledge:
new AssistantConfig(
provider: $provider,
storage: new FileStorage(__DIR__ . '/storage'),
autoLearn: true,
requireLearningCheck: true, // Default: mandatory check before using tools
)When auto-learning is enabled, the assistant gains access to 5 contextual learning tools:
| Tool | Purpose |
|---|---|
record_learning |
Record a pattern or anti-pattern for a specific context |
get_context_insights |
Retrieve recorded learnings, patterns, and known issues |
record_bug |
Document a bug or error with optional workaround |
find_similar_issues |
Search for known issues before attempting an approach |
forget_learning |
Remove a learning entry (with anti-poisoning guardrails) |
Learnings are organized by context (tool name, framework, domain):
// Record a successful pattern
$assistant->chat(new UserMessage(
'record_learning(context: "filesystem_tool", observation: "Always check parent dir", worked_well: true)'
));
// Check insights before using a tool
$assistant->chat(new UserMessage(
'get_context_insights(context: "database_tool")'
));When requireLearningCheck: true (default), the assistant is instructed to consult the learning system before using any tool for the first time in a conversation. This prevents repeated mistakes and leverages accumulated knowledge.
To disable:
new AssistantConfig(
provider: $provider,
storage: $storage,
requireLearningCheck: false, // Skip mandatory checks
)The learning system has built-in protection against knowledge base poisoning:
- The assistant never records learnings directly dictated by the user
- Learnings must originate from the agent's own observations (tool results, error patterns, code analysis)
- If the user suggests a learning, the agent evaluates it critically and only records independently verified observations
- Instruction-like patterns such as "never use tool X" are detected and rejected by the
GuardsAgainstPoisoningtrait - Deletion requests are also guarded against bulk manipulation patterns (
forget all,purge, etc.)
This ensures the learning system cannot be manipulated through social engineering.
Provide per-user persistent memories scoped by a backend-provided user ID:
$assistant = Assistant::configure(
new AssistantConfig(
provider: new Anthropic('key', 'claude-sonnet-4'),
storage: new FileStorage(__DIR__ . '/storage'),
userId: $authenticatedUser->getId(), // Backend-provided, never from user input
)
);When userId is provided, the assistant gains 3 memory tools:
| Tool | Purpose |
|---|---|
save_memory |
Save a memory for the current user (category: preference, context, note, instruction) |
recall_memories |
Search and retrieve memories for the current user |
delete_memory |
Delete a specific memory by ID (ownership verified) |
userIdis injected by the backend viaAssistantConfig— the LLM cannot change it- Storage is partitioned by user ID via namespace (
memories/{userId}) delete_memoryverifies ownership before deletion- One user cannot access or modify another user's memories
Built-in tool for reading and extracting text from local documents:
use HackLab\AIAssistant\Tools\FileReader\FileReaderTool;
new AssistantConfig(
provider: $provider,
storage: $storage,
tools: [new FileReaderTool()],
);The assistant gains the read_file tool which accepts file_path (required) and max_length (optional, default 100k chars).
| Format | Extension | Dependency |
|---|---|---|
.pdf |
smalot/pdfparser (pure PHP) |
|
| Word | .docx |
phpoffice/phpword (pure PHP) |
| Plain Text | .txt |
Native |
| CSV | .csv |
Native |
| Markdown | .md |
Native |
| HTML | .html, .htm |
Native |
| JSON | .json |
Native |
| XML | .xml |
Native |
| RTF | .rtf |
Native |
Return typed PHP objects instead of plain text. Ideal for generating application configuration, extracting data, or building JSON APIs.
use NeuronAI\StructuredOutput\SchemaProperty;
class MapLayer
{
#[SchemaProperty(description: 'Layer name', required: true)]
public string $name;
#[SchemaProperty(description: 'Layer type: raster, vector, tile', required: true)]
public string $type;
#[SchemaProperty(description: 'Source URL', required: true)]
public string $source;
#[SchemaProperty(description: 'Default visibility', required: false)]
public bool $visible = true;
}
class MapConfig
{
#[SchemaProperty(description: 'Map title', required: true)]
public string $title;
#[SchemaProperty(description: 'Map layers', required: true, anyOf: [MapLayer::class])]
public array $layers;
}$assistant = Assistant::configure(
new AssistantConfig(
provider: new Anthropic('key', 'claude-sonnet-4'),
storage: new FileStorage(__DIR__ . '/storage'),
instructions: 'You configure interactive maps.',
outputClass: MapConfig::class,
)
);
$config = $assistant->structured(
new UserMessage('Street map of São Paulo with satellite overlay')
);
// Use as object
echo $config->title;
foreach ($config->layers as $layer) {
echo $layer->name;
}
// Or return as JSON to a frontend
header('Content-Type: application/json');
echo json_encode($config);$assistant = Assistant::configure(
new AssistantConfig(
provider: $provider,
storage: $storage,
instructions: 'Extract contact info from text.',
)
);
$contact = $assistant->structured(
new UserMessage('Alice, alice@example.com, (11) 99999-0000'),
ContactInfo::class,
);See API Reference for full details including validation rules and nested objects.
Interactive command-line assistant:
// See examples/cli-assistant.php
php examples/cli-assistant.phpFull architecture documentation is available in the docs/ directory:
- Architecture Overview
- Core Concepts
- Context Condenser
- Sub-Agent System
- Skill System
- MCP Integration
- Auto-Learning
- Persistence
- API Reference (includes Structured Output)
- Examples (includes Map Config, Data Extraction, Reports)
- Development Guide
- Security Audit
vendor/bin/phpunitBSD 3-Clause License
Contributions are welcome! Please ensure:
- PHP 8.2+ with
declare(strict_types=1) - PSR-12 coding standards
- Tests for new features
- All documentation in English