Introduction

What Laraclaw is, who it's for, the moving parts, and how the agent loop works.

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

TermLives whereRun byWhen to use
ConnectorBuilt-inWebhook / poll / CLIWire up a new chat surface
ToolBuilt-in or customAgent (model decides)Give the agent a new capability
TinkerBuilt-in (one tool)Agent (model decides)Open the lid on full PHP and shell access
Skilllaraclaw/skills/{name}/SKILL.mdAgent (model decides)Reusable procedure or output format
Personalaraclaw/personas/{name}.mdPer-thread, switched explicitlyChange tone and writing style
CommandService-provider registrationRuns before the agentDeterministic 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:

  1. A connector receives a message — Telegram pings the webhook, IMAP polls a new email, you type into the terminal.
  2. It builds an IncomingMessage — a DTO with a UUID, the text, any inbound attachments, and a key identifying the conversation.
  3. The CommandRegistry checks for an exact-match command. If one matches (like !new), it handles the message and the agent is never invoked.
  4. A Thread is found or created — keyed by (connector, key). This is the persistent conversation record. It stores the laravel/ai conversation ID, so multi-turn context survives across messages.
  5. The ChatBotAgent runs — the system prompt, current persona, and tool list get assembled, and laravel/ai does the heavy lifting of the chat completion + tool-calling loop.
  6. 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_:

TablePurposeKey columns
laraclaw_accountsMaps a connector-side identifier (Slack user ID, Telegram chat ID, email address, API token hash) to your users tableuser_id, connector, account
laraclaw_threadsOne row per ongoing conversation. The unit the agent reasons aboutconnector, key, conversation_id, is_direct_message, persona
laraclaw_remindersOne-shot scheduled promptsuser_id, connector, key, message, remind_at, sent_at
laraclaw_heartbeatsRecurring scheduled prompts driven by cron expressionsuser_id, connector, key, prompt, cron, is_active, last_run_at
laraclaw_embeddingsRAG memory chunks. One row per chunk, keyed by content hashuser_id, source_type, source_id, content, content_hash, embedding, metadata

A few things worth flagging:

  • laraclaw_embeddings is 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_id references a UUID owned by laravel/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_id even 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!

Copyright © 2026