[{"data":1,"prerenderedAt":697},["ShallowReactive",2],{"navigation_docs":3,"-deployment":139,"-deployment-surround":692},[4,8,12,16,20,24,28,32,59,63,117,121,135],{"title":5,"path":6,"stem":7},"Introduction","\u002Fintroduction","1.introduction",{"title":9,"path":10,"stem":11},"Memory","\u002Fmemory","10.memory",{"title":13,"path":14,"stem":15},"Customization","\u002Fcustomization","11.customization",{"title":17,"path":18,"stem":19},"Deployment","\u002Fdeployment","12.deployment",{"title":21,"path":22,"stem":23},"Reference","\u002Freference","13.reference",{"title":25,"path":26,"stem":27},"Quickstart","\u002Fquickstart","2.quickstart",{"title":29,"path":30,"stem":31},"Installation","\u002Finstallation","3.installation",{"title":33,"icon":34,"path":35,"stem":36,"children":37,"page":58},"Connectors","i-lucide-cable","\u002Fconnectors","4.connectors",[38,42,46,50,54],{"title":39,"path":40,"stem":41},"Telegram","\u002Fconnectors\u002Ftelegram","4.connectors\u002F1.telegram",{"title":43,"path":44,"stem":45},"Slack","\u002Fconnectors\u002Fslack","4.connectors\u002F2.slack",{"title":47,"path":48,"stem":49},"Email","\u002Fconnectors\u002Femail","4.connectors\u002F3.email",{"title":51,"path":52,"stem":53},"API","\u002Fconnectors\u002Fapi","4.connectors\u002F4.api",{"title":55,"path":56,"stem":57},"Terminal","\u002Fconnectors\u002Fterminal","4.connectors\u002F5.terminal",false,{"title":60,"path":61,"stem":62},"Attachments","\u002Fattachments","5.attachments",{"title":64,"icon":65,"path":66,"stem":67,"children":68,"page":58},"Tools","i-lucide-wrench","\u002Ftools","6.tools",[69,73,77,81,85,89,93,97,101,105,109,113],{"title":70,"path":71,"stem":72},"File Manager","\u002Ftools\u002Ffile-manager","6.tools\u002F1.file-manager",{"title":74,"path":75,"stem":76},"Text-to-Speech","\u002Ftools\u002Ftext-to-speech","6.tools\u002F10.text-to-speech",{"title":78,"path":79,"stem":80},"Confirmations","\u002Ftools\u002Fconfirmations","6.tools\u002F11.confirmations",{"title":82,"path":83,"stem":84},"Adding Custom Tools","\u002Ftools\u002Fadding-custom-tools","6.tools\u002F12.adding-custom-tools",{"title":86,"path":87,"stem":88},"Image Manager","\u002Ftools\u002Fimage-manager","6.tools\u002F2.image-manager",{"title":90,"path":91,"stem":92},"Web Request","\u002Ftools\u002Fweb-request","6.tools\u002F3.web-request",{"title":94,"path":95,"stem":96},"Reminders","\u002Ftools\u002Freminders","6.tools\u002F4.reminders",{"title":98,"path":99,"stem":100},"Heartbeats","\u002Ftools\u002Fheartbeats","6.tools\u002F5.heartbeats",{"title":102,"path":103,"stem":104},"Email Manager","\u002Ftools\u002Femail-manager","6.tools\u002F6.email-manager",{"title":106,"path":107,"stem":108},"Calendar","\u002Ftools\u002Fcalendar","6.tools\u002F7.calendar",{"title":110,"path":111,"stem":112},"Read Database","\u002Ftools\u002Fread-database","6.tools\u002F8.read-database",{"title":114,"path":115,"stem":116},"Headless Browser","\u002Ftools\u002Fbrowser","6.tools\u002F9.browser",{"title":118,"path":119,"stem":120},"Tinker","\u002Ftinker","7.tinker",{"title":122,"icon":123,"path":124,"stem":125,"children":126,"page":58},"Skills","i-lucide-sparkles","\u002Fskills","8.skills",[127,131],{"title":128,"path":129,"stem":130},"Overview","\u002Fskills\u002Foverview","8.skills\u002F1.overview",{"title":132,"path":133,"stem":134},"Adding Custom Skills","\u002Fskills\u002Fadding-custom-skills","8.skills\u002F2.adding-custom-skills",{"title":136,"path":137,"stem":138},"Personas","\u002Fpersonas","9.personas",{"id":140,"title":17,"body":141,"description":686,"extension":687,"links":688,"meta":689,"navigation":576,"path":18,"seo":690,"stem":19,"__hash__":691},"docs\u002F12.deployment.md",{"type":142,"value":143,"toc":673},"minimark",[144,148,152,155,158,162,165,191,202,211,216,243,246,257,261,268,275,278,282,285,299,302,310,313,329,332,336,339,354,366,370,377,397,404,446,449,452,462,473,484,487,491,498,501,555,558,562,565,663,666,669],[145,146,5],"h2",{"id":147},"introduction",[149,150,151],"p",{},"The terminal loop is great for development — you type, the agent replies, you iterate. But production is different. There's no friendly REPL keeping things alive; you need long-running processes, supervisors, and a couple of supporting services.",[149,153,154],{},"This page walks through everything you need running in production, and ends with a pre-launch checklist you can copy\u002Fpaste into your runbook.",[149,156,157],{},"Let's get started!",[145,159,161],{"id":160},"queue-worker","Queue Worker",[149,163,164],{},"Almost every connector queues the agent invocation rather than running it during the HTTP request:",[166,167,172],"pre",{"className":168,"code":169,"language":170,"meta":171,"style":171},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","php artisan queue:work\n","bash","",[173,174,175],"code",{"__ignoreMap":171},[176,177,180,184,188],"span",{"class":178,"line":179},"line",1,[176,181,183],{"class":182},"sBMFI","php",[176,185,187],{"class":186},"sfazB"," artisan",[176,189,190],{"class":186}," queue:work\n",[149,192,193,194,201],{},"In production, run it under ",[195,196,200],"a",{"href":197,"rel":198},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fhorizon",[199],"nofollow","Laravel Horizon",", Supervisor, or systemd so it restarts on failure. Without a queue worker, messages arrive but are never processed — they just pile up in the queue.",[203,204,205],"blockquote",{},[149,206,207,210],{},[176,208,209],{},"!NOTE","\nThe API connector is the exception. It runs synchronously and returns the agent's reply inline, so it works without a queue worker. Every other connector (Telegram, Slack, Email, Terminal) needs one.",[212,213,215],"h3",{"id":214},"job-retries","Job Retries",[149,217,218,219,222,223,222,226,229,230,234,235,238,239,242],{},"Laraclaw's jobs (",[173,220,221],{},"SendReminder",", ",[173,224,225],{},"SendHeartbeat",[173,227,228],{},"EmbedConversation",", the per-connector message processing jobs) do ",[231,232,233],"strong",{},"not"," declare a ",[173,236,237],{},"$tries"," count and do not configure backoff. They get whatever your queue connection's default is — which on a fresh Laravel install is 1 attempt, fail to ",[173,240,241],{},"failed_jobs",", no retry.",[149,244,245],{},"If you want Laraclaw jobs to retry on transient failures (network blips, provider rate limits), set a default on your queue connection or wrap a custom listener around the events you care about.",[203,247,248],{},[149,249,250,253,254,256],{},[176,251,252],{},"!IMPORTANT","\nMonitor ",[173,255,241],{}," in production. A stuck reminder or a backed-up embedding queue will sit there silently — nothing will tell you about it unless you're watching.",[212,258,260],{"id":259},"reloading-skills-personas-and-prompts","Reloading Skills, Personas, and Prompts",[149,262,263,264,267],{},"The ",[173,265,266],{},"SkillRegistry"," is a singleton scanned once per process. Personas and the base system prompt are read from disk on every agent turn, but the file paths are resolved once at registration.",[149,269,270,271,274],{},"So after editing anything under ",[173,272,273],{},"laraclaw\u002F"," (instructions, personas, or skills), restart the queue worker (and Horizon supervisors, if you use them) to pick up the changes.",[149,276,277],{},"The terminal connector and the API connector boot a fresh process per invocation, so they see edits immediately. No restart needed there.",[145,279,281],{"id":280},"scheduler","Scheduler",[149,283,284],{},"Reminders and heartbeats are dispatched by scheduled commands. You need the Laravel scheduler running:",[166,286,288],{"className":168,"code":287,"language":170,"meta":171,"style":171},"php artisan schedule:work\n",[173,289,290],{"__ignoreMap":171},[176,291,292,294,296],{"class":178,"line":179},[176,293,183],{"class":182},[176,295,187],{"class":186},[176,297,298],{"class":186}," schedule:work\n",[149,300,301],{},"Or, in production, a cron entry:",[166,303,308],{"className":304,"code":306,"language":307},[305],"language-text","* * * * * cd \u002Fpath\u002Fto\u002Fapp && php artisan schedule:run >> \u002Fdev\u002Fnull 2>&1\n","text",[173,309,306],{"__ignoreMap":171},[149,311,312],{},"The service provider registers two commands to run every minute:",[314,315,316,323],"ul",{},[317,318,319,322],"li",{},[173,320,321],{},"laraclaw:send-due-reminders"," — fires any one-shot reminders whose time has come.",[317,324,325,328],{},[173,326,327],{},"laraclaw:process-heartbeats"," — evaluates each active heartbeat's cron expression and dispatches matches.",[149,330,331],{},"Without the scheduler, reminders and heartbeats accumulate but never fire. Don't skip this one.",[145,333,335],{"id":334},"redis","Redis",[149,337,338],{},"Laraclaw uses Redis for two things:",[340,341,342,348],"ol",{},[317,343,344,347],{},[231,345,346],{},"The confirmation flow."," When a tool needs confirmation (like deleting a file), the pending state is stored in Redis until the user replies \"yes\" or \"no\" through the same connector.",[317,349,350,353],{},[231,351,352],{},"The default queue driver."," You can use any queue driver, but Redis is the easiest in production.",[149,355,356,357,361,362,365],{},"The service provider also registers a ",[358,359,360],"em",{},"blocking"," Redis connection used internally by the confirmation ",[173,363,364],{},"BLPOP"," calls. There's nothing to configure manually — but Redis itself must be running.",[145,367,369],{"id":368},"imap-listener","IMAP Listener",[149,371,372,373,376],{},"If you've enabled the ",[195,374,375],{"href":48},"Email connector",", the inbound mail listener must be running:",[166,378,380],{"className":168,"code":379,"language":170,"meta":171,"style":171},"php artisan imap:watch default --with=headers,body\n",[173,381,382],{"__ignoreMap":171},[176,383,384,386,388,391,394],{"class":178,"line":179},[176,385,183],{"class":182},[176,387,187],{"class":186},[176,389,390],{"class":186}," imap:watch",[176,392,393],{"class":186}," default",[176,395,396],{"class":186}," --with=headers,body\n",[149,398,399,400,403],{},"It's a long-running command from ",[173,401,402],{},"directorytree\u002Fimapengine-laravel",". Run it under Supervisor or systemd:",[166,405,409],{"className":406,"code":407,"language":408,"meta":171,"style":171},"language-ini shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[program:laraclaw-imap]\ncommand=php \u002Fvar\u002Fwww\u002Fapp\u002Fartisan imap:watch default --with=headers,body\nautostart=true\nautorestart=true\nuser=www-data\nstdout_logfile=\u002Fvar\u002Flog\u002Flaraclaw-imap.log\n","ini",[173,410,411,416,422,428,434,440],{"__ignoreMap":171},[176,412,413],{"class":178,"line":179},[176,414,415],{},"[program:laraclaw-imap]\n",[176,417,419],{"class":178,"line":418},2,[176,420,421],{},"command=php \u002Fvar\u002Fwww\u002Fapp\u002Fartisan imap:watch default --with=headers,body\n",[176,423,425],{"class":178,"line":424},3,[176,426,427],{},"autostart=true\n",[176,429,431],{"class":178,"line":430},4,[176,432,433],{},"autorestart=true\n",[176,435,437],{"class":178,"line":436},5,[176,438,439],{},"user=www-data\n",[176,441,443],{"class":178,"line":442},6,[176,444,445],{},"stdout_logfile=\u002Fvar\u002Flog\u002Flaraclaw-imap.log\n",[149,447,448],{},"If the listener stops, inbound email stops being delivered. Everything else (Slack, Telegram, API, terminal) keeps working — it's an isolated failure.",[145,450,451],{"id":451},"pgvector",[149,453,454,455,458,459,461],{},"If you've enabled ",[195,456,457],{"href":10},"memory"," and your database is PostgreSQL, install the ",[173,460,451],{}," extension on the server:",[166,463,467],{"className":464,"code":465,"language":466,"meta":171,"style":171},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","CREATE EXTENSION vector;\n","sql",[173,468,469],{"__ignoreMap":171},[176,470,471],{"class":178,"line":179},[176,472,465],{},[149,474,475,476,479,480,483],{},"Run this ",[231,477,478],{},"before"," the Laraclaw migrations. The embeddings migration detects pgvector at install time and creates either a native ",[173,481,482],{},"vector"," column or a JSON fallback. Switching after the fact requires a manual migration — so get this right up front.",[149,485,486],{},"Without PostgreSQL or pgvector, the JSON fallback works on any database. It's slower past a few thousand embeddings but functionally equivalent.",[145,488,490],{"id":489},"multiple-nodes","Multiple Nodes",[149,492,493,494,497],{},"Now, I know what you might be thinking: ",[358,495,496],{},"can I run this across multiple web nodes?"," The honest answer: Laraclaw has not been validated against multi-node deployments, and several known sharp edges make it risky.",[149,499,500],{},"Here's what to watch for:",[314,502,503,523,532,542],{},[317,504,505,508,509,511,512,514,515,518,519,522],{},[231,506,507],{},"Scheduler duplication."," The package registers ",[173,510,321],{}," and ",[173,513,327],{}," with no ",[173,516,517],{},"onOneServer()"," lock. If your cron runs on more than one host, every minute ",[358,520,521],{},"both"," hosts will dispatch the same reminders and heartbeats. Pin the scheduler to exactly one node as the simplest fix.",[317,524,525,531],{},[231,526,527,530],{},[173,528,529],{},"imap:watch"," is single-instance."," Two listeners against the same mailbox will both see every message and dispatch it twice.",[317,533,534,537,538,541],{},[231,535,536],{},"Attachments must live on a shared disk."," Inbound files written by a connector worker on one node need to be readable by an agent job on another. Point ",[173,539,540],{},"LARACLAW_ATTACHMENTS_DISK"," at S3 or another network filesystem.",[317,543,544,547,548,550,551,554],{},[231,545,546],{},"Confirmation flow assumes one Redis."," The blocking ",[173,549,364],{}," and the ",[173,552,553],{},"awaiting_confirm:"," key both live on the same Redis connection. As long as every node points at the same Redis, this is fine.",[149,556,557],{},"In short: run on a single host until you have a real reason not to. The single-owner architecture means you almost certainly don't need horizontal scaling.",[145,559,561],{"id":560},"production-checklist","Production Checklist",[149,563,564],{},"Run through this before flipping the bot live:",[314,566,569,583,592,598,607,616,622,631,637,648,657],{"className":567},[568],"contains-task-list",[317,570,573,578,579,582],{"className":571},[572],"task-list-item",[574,575],"input",{"disabled":576,"type":577},true,"checkbox"," ",[173,580,581],{},"LARACLAW_ADMIN_USER_ID"," is set and points to a real user",[317,584,586,578,588,591],{"className":585},[572],[574,587],{"disabled":576,"type":577},[173,589,590],{},"AI_DEFAULT"," is set and your provider key is configured",[317,593,595,597],{"className":594},[572],[574,596],{"disabled":576,"type":577}," At least one connector is enabled and its webhook is registered",[317,599,601,578,603,606],{"className":600},[572],[574,602],{"disabled":576,"type":577},[173,604,605],{},"php artisan queue:work"," is running under a process supervisor",[317,608,610,578,612,615],{"className":609},[572],[574,611],{"disabled":576,"type":577},[173,613,614],{},"schedule:work"," or a cron entry is in place",[317,617,619,621],{"className":618},[572],[574,620],{"disabled":576,"type":577}," Redis is running and reachable",[317,623,625,627,628,630],{"className":624},[572],[574,626],{"disabled":576,"type":577}," If email is enabled, ",[173,629,529],{}," is running under a supervisor",[317,632,634,636],{"className":633},[572],[574,635],{"disabled":576,"type":577}," If memory is enabled, embeddings are configured and pgvector is installed (if applicable)",[317,638,640,578,642,645,646],{"className":639},[572],[574,641],{"disabled":576,"type":577},[173,643,644],{},"LARACLAW_LOG_AGENT_REQUESTS=true"," if you've enabled ",[195,647,118],{"href":119},[317,649,651,578,653,656],{"className":650},[572],[574,652],{"disabled":576,"type":577},[173,654,655],{},"LARACLAW_WEBHOOK_RATE_LIMIT"," is set to a sane value for your traffic",[317,658,660,662],{"className":659},[572],[574,661],{"disabled":576,"type":577}," You've sent a test message through every enabled connector and confirmed the agent replies",[149,664,665],{},"Tick all of those? You're ready!",[149,667,668],{},"Until next time!",[670,671,672],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":171,"searchDepth":418,"depth":418,"links":674},[675,676,680,681,682,683,684,685],{"id":147,"depth":418,"text":5},{"id":160,"depth":418,"text":161,"children":677},[678,679],{"id":214,"depth":424,"text":215},{"id":259,"depth":424,"text":260},{"id":280,"depth":418,"text":281},{"id":334,"depth":418,"text":335},{"id":368,"depth":418,"text":369},{"id":451,"depth":418,"text":451},{"id":489,"depth":418,"text":490},{"id":560,"depth":418,"text":561},"What you need running in production for Laraclaw to work end-to-end.","md",null,{},{"title":17,"description":686},"OSyH-gyy3nINHCwFK_DlvkyEcDO5ejRoLrxghrEw5A8",[693,695],{"title":13,"path":14,"stem":15,"description":694,"children":-1},"Bend Laraclaw to your needs without writing a tool — system prompts, rate limiting, logging, custom commands, and event hooks.",{"title":21,"path":22,"stem":23,"description":696,"children":-1},"Every environment variable Laraclaw reads, with its default and what it controls.",1786661906119]