Introduction
What Is Laraclaw
You've built a Laravel app and you'd love to talk to it. Not click around it — talk to it. You want to ask "any new orders?" from Telegram, drop a PDF into a Slack DM and have it filed, or receive a report via email every morning.
That's Laraclaw.
Laraclaw is a Laravel package that turns your application into an AI assistant you can reach from five surfaces: Telegram, Slack, Email, an HTTP API, and the terminal. The agent reads your messages, calls tools (read a file, query the database, schedule a reminder, send an email), and replies through the same channel you wrote in.
It's built on top of laravel/ai, so the actual model — Anthropic, OpenAI, Mistral, Ollama, anything else — is whatever you've configured. Laraclaw handles the surrounding pieces: routing messages, persisting conversations, executing tools, scheduling reminders, retrieving past context, and keeping the whole thing safe.
The One-Owner Model
Here's the most important thing to understand up front: Laraclaw is single-user.
Every install has exactly one owner — one user, identified by LARACLAW_ADMIN_USER_ID. The owner is the only identity the agent ever speaks for. There is no multi-tenant mode, no per-user data scoping, no role system. If the agent reads the calendar, it's the owner's calendar. If it sends an email, it's from the owner's mailbox. If it runs Tinker, it's with the owner's full privileges.
Now, you might be wondering: can other people still talk to it? Yes! The owner can drop the bot into a shared Slack channel, a Telegram group, or a server's terminal — anyone in those rooms can prompt the agent and the agent will reply. But every tool call still runs as the owner.
DM connectors (Telegram DM, Slack DM, Email) are stricter: they only respond to addresses registered in the laraclaw_accounts table, all of which point at the owner. Any other sender is silently dropped.
!IMPORTANT If you need a multi-tenant assistant where every user has their own data scope, Laraclaw is not the right fit. The whole package is designed around "one owner, one trust boundary."
The Building Blocks
Laraclaw has six terms that show up over and over. They overlap a little in plain English, so let's pin them down before going further.
Connectors
A connector is an inbound/outbound transport. Each one knows how to receive a message (a webhook, an IMAP poll, a CLI prompt) and how to send a reply back the same way. We ship five: Telegram, Slack, Email, the HTTP API, and the Terminal. Enable the ones you need; the rest stay dormant.
Tools
A tool is a function the agent can call mid-turn. Tools are how the agent does anything beyond producing text — read a file, schedule a reminder, send an email, query the database. Laraclaw ships a fixed set of built-in tools and lets you add your own. Browse them under Tools.
Tinker
Tinker is a tool with unrestricted access to your application: arbitrary PHP via php artisan tinker --execute, plus shell access via Process::run. It lives in its own page because it's disabled by default and warrants a separate trust decision. If you turn it on, the agent can execute any code your PHP user can.
Skills
A skill is a Markdown file the agent reads on demand to follow a procedure. Think of it as guidance, not code: it tells the model "when you're asked to write release notes, structure the output like this." The agent decides which skill applies; Laraclaw just makes them discoverable. See Skills.
Personas
A persona is a Markdown file appended to the system prompt. Personas change how the agent talks — its tone, focus, and conventions — without touching its capabilities. One thread can have one active persona at a time. See Personas.
Commands
A command is a deterministic shortcut that runs before the agent loop and never reaches the model. The built-in !new command resets the current conversation. Commands are how you wire fast, free, predictable shortcuts for things you don't want the model second-guessing. See Custom Commands.
Quick Comparison
| Term | Lives where | Run by | When to use |
|---|---|---|---|
| Connector | Built-in | Webhook / poll / CLI | Wire up a new chat surface |
| Tool | Built-in or custom | Agent (model decides) | Give the agent a new capability |
| Tinker | Built-in (one tool) | Agent (model decides) | Open the lid on full PHP and shell access |
| Skill | laraclaw/skills/{name}/SKILL.md | Agent (model decides) | Reusable procedure or output format |
| Persona | laraclaw/personas/{name}.md | Per-thread, switched explicitly | Change tone and writing style |
| Command | Service-provider registration | Runs before the agent | Deterministic shortcut, no AI |
The Agent Loop
Every message goes through the same pipeline, regardless of which connector it came in on. Let's trace it:
Connector (webhook, IMAP, CLI)
│
▼
IncomingMessage (uuid, text, attachments, key)
│
▼
CommandRegistry::match() ← short-circuit if a command matches
│
▼
Thread::forMessage() ← persona, conversation_id, is_direct_message
│
▼
ChatBotAgent (laravel/ai)
• system prompt = default + persona + current time
• tools: built-in + conditional + custom
• middleware: TranscribeAudio, EmbedConversation, LogAgentRequest
│
▼
Connector::reply($thread, $text, $outboundAttachments)
Here's what's happening:
- A connector receives a message — Telegram pings the webhook, IMAP polls a new email, you type into the terminal.
- It builds an
IncomingMessage— a DTO with a UUID, the text, any inbound attachments, and akeyidentifying the conversation. - The
CommandRegistrychecks for an exact-match command. If one matches (like!new), it handles the message and the agent is never invoked. - A
Threadis found or created — keyed by(connector, key). This is the persistent conversation record. It stores thelaravel/aiconversation ID, so multi-turn context survives across messages. - The
ChatBotAgentruns — the system prompt, current persona, and tool list get assembled, andlaravel/aidoes the heavy lifting of the chat completion + tool-calling loop. - The connector replies — back through the same channel, with any files the agent staged for delivery.
For DMs the key is the user's account identifier; for groups it's the channel or thread identifier.
The Data Model
Laraclaw publishes five tables. All of them are prefixed laraclaw_:
| Table | Purpose | Key columns |
|---|---|---|
laraclaw_accounts | Maps a connector-side identifier (Slack user ID, Telegram chat ID, email address, API token hash) to your users table | user_id, connector, account |
laraclaw_threads | One row per ongoing conversation. The unit the agent reasons about | connector, key, conversation_id, is_direct_message, persona |
laraclaw_reminders | One-shot scheduled prompts | user_id, connector, key, message, remind_at, sent_at |
laraclaw_heartbeats | Recurring scheduled prompts driven by cron expressions | user_id, connector, key, prompt, cron, is_active, last_run_at |
laraclaw_embeddings | RAG memory chunks. One row per chunk, keyed by content hash | user_id, source_type, source_id, content, content_hash, embedding, metadata |
A few things worth flagging:
laraclaw_embeddingsis shared per owner. There is exactly one memory pool per install, and every retrieval pulls from it. Threads do not have their own memory.laraclaw_threads.conversation_idreferences a UUID owned bylaravel/ai's own tables — the actual message history lives there. We deliberately don't add a foreign key to avoid coupling our migrations to a third-party schema.- Everything is owner-scoped via
user_ideven though there's only one owner. The column is there so you can clear or audit per user if you ever migrate.
What's Next
Now you know the moving parts. Pick where you want to go:
- Quickstart — install, run the wizard, send your first message in five minutes.
- Installation — the full install reference.
- Connectors — wire up the chat surfaces your owner will use.
- Tools — give the agent the capabilities it needs to be useful.
- Customization — bend Laraclaw without writing a tool: rate limits, prompts, commands, events.
Ready? Let's dive in!