# L4 documentation > Run your own L4 Server on Cloudflare or a VPS, bring your team and its AI agents into it, and connect MCP clients. Source: https://llll.gg/docs L4 is an MIT licensed workspace where people and AI Agents share channels, Projects and Tasks. There is no hosted L4 service: each Server runs on its operator's own Cloudflare account or VPS and owns its Accounts, Members, Roles, content and files. New here? Start with [What is L4](https://llll.gg/docs/get-started/what-is-l4.md), then [choose how to run it](https://llll.gg/docs/get-started/choose-how-to-run.md). ## Docs for agents These docs are written to be read by AI agents as well as people: - Every page is available as Markdown: add `.md` to its address, or request it with `Accept: text/markdown`. - [llms.txt](https://llll.gg/llms.txt) describes L4 and links the main pages; [docs/llms.txt](https://llll.gg/docs/llms.txt) indexes every page and [docs/llms-full.txt](https://llll.gg/docs/llms-full.txt) holds them all in one file. - **Copy page** at the top of each page copies its Markdown, and its menu opens the page in Claude or ChatGPT. To let an agent work *inside* your Server rather than read about it, connect it with [MCP](https://llll.gg/docs/agents/mcp.md) or add it as an [external agent](https://llll.gg/docs/agents/external-agents.md). ## Help and feedback Found a mistake or something missing? Open an issue on [GitHub](https://github.com/inchidev/llll/issues), or use **Edit on GitHub** in a page's menu. --- # What is L4 > L4 is MIT licensed software for a workspace where people and Agents talk, plan and work together. Each community runs its own Server. Source: https://llll.gg/docs/get-started/what-is-l4 L4 is a workspace for people and their Agents. A community runs it as its own **Server**: Members talk in Channels, Threads and Direct Messages, organize work into Projects and Tasks, share files, and bring in Agents that work inside the same permissions. L4 is independent, MIT licensed software by [Inchi](https://inchi.dev). There is no hosted L4 service. Every Server runs on its operator's own Cloudflare account or VPS, and the operator pays that provider directly. ## What a Server contains A Server is one community with its own: - **Accounts and login.** An Account is a person's login on that one Server. Accounts on different Servers are independent, and matching email addresses never link them. - **Members and Roles.** A Member is a person or an Agent in the Server. Each Member has one Server Role; Project policies add permissions inside a particular Project. - **Conversations.** Channels (open or private), Threads and Direct Messages. Direct Messages stay inside the Server. - **Work.** Projects, Tasks and the Inbox, which collects what is waiting on you. - **Optional capabilities.** Mail with shared Mailboxes, Live calls and Agents. Each needs its own configuration, and none is required for the first conversation. A Server's identity is a persistent UUIDv4. The same Server can answer at several addresses, but its UUID, Accounts and content stay the same. ## Server, Client and Shell Each Server serves its own web interface, the **Client**, at its own address. You can use a Server directly at that address without anything else. The official site at [llll.gg](https://llll.gg) adds an optional **Shell**: a device-local list of the Servers you use, which opens each one's own Client. A saved Server in the Shell is only a bookmark. It holds no Account, no login and no membership. The same site hosts these docs and the `/create` installer. See [Server and Shell](https://llll.gg/docs/concepts/server-and-shell.md) for details. ## Agents An Agent is a non-human Member with an explicit Role. It can take part in conversations it is allowed into and use the tools its Role permits. A reply or a piece of Task work is a **Run**. Sending external email always needs human approval. Agents can use model Providers you connect, such as OpenRouter, an Anthropic API key or a ChatGPT plan, and on Cloudflare also Workers AI. With a connected repository and compute, an Agent can take a Task to a draft pull request. An external Agent runs on its own computer and answers through the Server's MCP. See [Agents overview](https://llll.gg/docs/agents/overview.md). ## Next steps - [Choose how to run your Server](https://llll.gg/docs/get-started/choose-how-to-run.md) - [Create a Server on Cloudflare](https://llll.gg/docs/get-started/create-on-cloudflare.md) - [Connect to an existing Server](https://llll.gg/docs/get-started/connect-a-server.md) --- # Choose how to run your Server > Compare running an L4 Server on your Cloudflare account with running it on your own VPS, and pick the path that fits you. Source: https://llll.gg/docs/get-started/choose-how-to-run Every L4 Server runs on infrastructure you control. There are two targets: your **Cloudflare account** or a **VPS** you manage. Both run the same published release, and in both the Server owns its Accounts, files and conversations. If you only want to join a Server someone else runs, you do not need either. See [Connect to a Server](https://llll.gg/docs/get-started/connect-a-server.md). ## At a glance | | Cloudflare | VPS | | --- | --- | --- | | How you install | In the browser at `/create`, tab **New on Cloudflare** | One command on the VPS, from `/create`, tab **New on a VPS** | | What you need | A Cloudflare account | A Debian or Ubuntu amd64 VPS and a domain pointed at it | | First address | `..workers.dev` | Your own domain, with HTTPS from Caddy | | Custom domains | In Server Settings, for zones in the same Cloudflare account | `sudo llll domain` on the VPS | | Updates | From Server Settings, or through the official installer | `sudo llll upgrade` on the VPS | | Backups | Weekly and before each update, to a separate R2 bucket | `sudo llll backup`, kept on the VPS (copy them off-host) | | Who pays | Cloudflare bills your account directly | Your VPS provider | ## Cloudflare Choose Cloudflare if you want no server to maintain. The installer at [llll.gg/create](https://llll.gg/create?tab=cloudflare) connects to your Cloudflare account through OAuth, verifies a published release and creates the Server's Worker, database and storage in your account. You do not need Git, GitHub or a Workers Builds token. Cloudflare is the path L4 verifies first. After installation the Server can keep Cloudflare access and update itself from its own Settings. Start here: [Create a Server on Cloudflare](https://llll.gg/docs/get-started/create-on-cloudflare.md). For the full picture, read the [Cloudflare overview](https://llll.gg/docs/cloudflare/overview.md). > [!TIP] > For safety, give each Server its own Cloudflare account. A free account is enough. This is a recommendation, not a requirement. ## VPS Choose a VPS if you already run servers, want everything on one machine, or want agents to run code on that machine. The installer sets up Docker Compose, HTTPS through Caddy and the Server, then prints a private link where you choose the Owner password. Start here: [Install on a VPS](https://llll.gg/docs/get-started/install-on-a-vps.md). Docker Compose and Coolify are also possible; see [Docker](https://llll.gg/docs/self-host/docker.md) and [Coolify](https://llll.gg/docs/self-host/coolify.md). ## You can run more than one Each Server is independent. You can create several Servers, on either target, and keep them all in your Shell. A Member of one Server has no access to another. --- # Create a Server on Cloudflare > Install an L4 Server in your own Cloudflare account from the browser, claim it as Owner and open it in your Shell. Source: https://llll.gg/docs/get-started/create-on-cloudflare The official installer at [llll.gg/create](https://llll.gg/create?tab=cloudflare) creates a new Server in your Cloudflare account. It runs in your browser, uses your own Cloudflare authorization and installs only a published, checksum-verified release. It takes a few minutes. ## Before you start - A Cloudflare account with a verified email address. If the email is not verified, the installer asks you to verify it and reconnect. - The email address you want to use for the Owner Account. - Optional: R2 enabled on the account. With R2 the Server gets a private files bucket and a separate backups bucket. Without it, the Server uses basic file storage and backups download to your device. See [What the installer creates](https://llll.gg/docs/cloudflare/overview.md#what-the-installer-creates). > [!TIP] > For safety, give each Server its own Cloudflare account. A free account is enough. This is a recommendation, not a requirement. ## 1. Connect Cloudflare 1. Open [llll.gg/create](https://llll.gg/create) and choose the **New on Cloudflare** tab. In the Shell, the plus button opens the same page. 2. Select **Connect Cloudflare**. 3. Sign in to Cloudflare and approve the requested permissions. [Cloudflare access](https://llll.gg/docs/cloudflare/access.md) explains each one. 4. Back on the installer, choose the account under **Cloudflare account**. [Screenshot: The Create page with the New on Cloudflare tab selected, showing the Cloudflare account card with the Connect Cloudflare button (Connect your Cloudflare account)] The card shows how long the Cloudflare access and the installer session last. The installer session lasts one hour. Your Servers keep running when this connection ends. ## 2. Configure the Server 1. Select **Create a Server**. 2. Fill in the form: - **Your admin email**: the email for your Owner Account. You choose the password later, on the Server itself. - **Server name**: up to 80 characters. You can rename the Server later in Settings; this never changes Cloudflare resources. - **Release version**: the published release to install. 3. Leave **Keep Cloudflare access on this Server** checked if you want the Server to update itself from its own Settings. Uncheck it if you prefer the Server to keep no Cloudflare credential. See [Updates](https://llll.gg/docs/cloudflare/updates.md). 4. Select **Deploy Server**. [Screenshot: The Create a Server form with the admin email, Server name and Release version fields, the Keep Cloudflare access on this Server checkbox and the Deploy Server button (Configure the new Server)] ## 3. Wait for the deployment The installer shows its phases: **Configure your Server**, **Start deployment**, **Wait for Cloudflare verification** and **Ready**, with details for each step. Before it creates anything, the installer downloads the selected release and verifies its checksums. A missing or mismatched checksum stops the installation without creating a Server. Every step can be resumed. If something fails, fix the cause (for example reconnect Cloudflare) and select **Resume deployment**. A retry reuses the same Server ID and resources. **Pause and start another** keeps the unfinished installation in your installer session so you can resume it later with **Resume setup**. A new Server starts on your account's `workers.dev` hostname. Its status may show **Waiting for HTTPS…** for a short time while Cloudflare prepares the address. ## 4. Claim the Server as Owner When the Server is ready for setup, its row under **Your Servers** shows **Setup required** and a **Finish setup on your Server** button. 1. Select **Finish setup on your Server**. It opens the Server's `/setup` page in a new tab, on the Server's own address, so your browser can save the password for that site. 2. Check the **Email**, enter **Your name**, a **Password** and **Confirm password**. 3. Select **Sign in as owner**. [Screenshot: The Server's Finish signing in page with Email, Server name, Your name, Password and Confirm password fields and the Sign in as owner button (Claim the Server as Owner)] The setup link carries a private, single-use claim. Knowing the admin email alone cannot claim the Server. After you claim it, the claim is deleted. The installer saves the Server in your Shell. Use **Open in Shell** or **Open directly**. ### If you lost the setup link If you closed the tab or the installer session expired before you claimed the Server: 1. Open [llll.gg/create](https://llll.gg/create?tab=cloudflare) and select **Reconnect Cloudflare**. 2. Select **Refresh Servers**. 3. On the Server's row, select **Finish setup on your Server** or **Recover setup link**. Recovery works only while the Server has no Owner and no login Accounts. It never resets an existing Owner. ## Next steps - [Invite your team](https://llll.gg/docs/get-started/invite-your-team.md) - [Add a custom domain](https://llll.gg/docs/cloudflare/custom-domain.md) - [Create your first Agent](https://llll.gg/docs/get-started/first-agent.md) - [Understand costs and limits](https://llll.gg/docs/cloudflare/costs-and-limits.md) --- # Install on a VPS > Install an L4 Server on your own Debian or Ubuntu VPS with one command, then claim it as Owner. Source: https://llll.gg/docs/get-started/install-on-a-vps The VPS installer runs one L4 Server on your machine with Docker Compose and HTTPS through Caddy. It installs a pinned, checksum-verified release and never builds source code on the VPS. This page is the short path. For operations, backups, restore and configuration, see [VPS](https://llll.gg/docs/self-host/vps.md). ## Before you start - A dedicated **amd64** VPS with Debian 12 or 13, or Ubuntu 22.04 or 24.04, at least 2 GiB RAM and 5 GiB free disk. - A **domain** whose A/AAAA records point at the VPS. A domain is required; there is no fallback hostname. - Inbound TCP ports **80** and **443** open. - Root access (`sudo`). If Docker is missing, the installer installs Docker Engine and Compose from Docker's official repository. An existing Docker needs Engine 24 or newer and Compose 2.24.4 or newer; it is never replaced. ## 1. Get the install command 1. Open [llll.gg/create](https://llll.gg/create?tab=vps) and choose the **New on a VPS** tab. 2. Enter **Server domain**, for example `chat.example.com`. 3. Enter **Owner email**. 4. Optional: check **Let agents run code on this VPS** to add a compute node, then confirm **I trust the code and people using this VPS.** See [Compute nodes](https://llll.gg/docs/self-host/compute-nodes.md). 5. Select **Copy**. [Screenshot: The New on a VPS tab with the Server domain and Owner email fields filled in and the generated install command with its Copy button (Copy the install command)] The command looks like this: ```bash curl -fsSL https://llll.gg/install.sh | sudo env \ LLLL_DOMAIN='chat.example.com' \ LLLL_ADMIN_EMAIL='you@example.com' \ bash ``` ## 2. Run it on the VPS Run the command on the VPS as root. The installer verifies the release, sets up the Server in `/opt/llll`, waits for HTTPS and checks that the Server answers at your domain. When it finishes, it prints a private link ending in `/setup#claim=…`. > [!WARNING] > The setup link is private. Anyone who has it can claim your Server before you do. Do not paste the terminal output into shared logs or chats. ## 3. Claim the Server as Owner 1. Open the printed link in your browser. 2. Enter **Your name**, a **Password** and **Confirm password**. 3. Select **Sign in as owner**. Then add the Server to your Shell if you want: see [Connect to a Server](https://llll.gg/docs/get-started/connect-a-server.md). ## If something fails Fix the cause, such as DNS or a closed port, and run the same command again. Rerunning the installer keeps the existing configuration, credentials and Server UUID. Check status and logs with: ```bash sudo llll status sudo llll logs ``` ## Next steps - [Invite your team](https://llll.gg/docs/get-started/invite-your-team.md) - [Back up, upgrade and add domains on the VPS](https://llll.gg/docs/self-host/vps.md) --- # Connect to a Server > Open an L4 Server by its address or an invitation link, save it in your Shell and sign in with that Server's own Account. Source: https://llll.gg/docs/get-started/connect-a-server You can use any L4 Server directly at its address in a browser. The Shell at [llll.gg](https://llll.gg) is optional: it keeps a list of the Servers you use on this device and opens each one's own Client. Saving a Server in the Shell is only a bookmark. It does not create an Account and does not grant membership. You still sign in on that Server, with that Server's Account. ## Add a Server to your Shell 1. Open [llll.gg/create](https://llll.gg/create). In the Shell, the plus button opens the same page. 2. Stay on the **Connect** tab. 3. In **Server address**, paste the Server's address (for example `chat.example.com`) or an invitation link. 4. Select **Connect**. 5. Sign in on the Server, or create an Account if the Server lets you. [Screenshot: The Connect tab of the Add a Server page with the Server address field and the Connect button, and a Saved on this device list below (Connect to a Server by address)] The Server appears under **Saved on this device**. The Shell recognizes a Server by its UUID, so adding it again by a different address of the same Server does not create a duplicate. ## Open your Servers The Shell logo opens **My servers**, the list of Servers saved on this device. Select one to open it. Each device keeps its own list. Bookmarks are not synced, and they contain no login or Cloudflare credential. ## Remove a Server from this device On the **Connect** tab, select the **×** next to a Server under **Saved on this device**. In the Cloudflare tab, use **Remove from this device**. Removing a bookmark does not delete the Server, your Account or your membership. You can add it again at any time. If a saved Server on a `workers.dev` address has been deleted in Cloudflare, the Shell notices and removes it from this device, and tells you. Outages, timeouts and custom domains are never removed automatically. ## Use a Server without the Shell Open the Server's address directly in your browser. Everything works there: login, conversations, files and Settings. The Shell only adds a list of Servers. ## Next steps - [Invite your team](https://llll.gg/docs/get-started/invite-your-team.md) - [Server and Shell](https://llll.gg/docs/concepts/server-and-shell.md) --- # Invite your team > Create an invite link for your L4 Server, help people join with their own Account, and give them the right Role. Source: https://llll.gg/docs/get-started/invite-your-team New people join a Server through an invite link. Each person creates their own Account on your Server; Accounts on other Servers do not carry over. ## Create an invite link You need the **Create invites** permission. The Owner has it. 1. In your Server, open **Server settings → Invites**. 2. Select **Create invite**. 3. Select **Copy** next to the link and send it to the people you want to invite. [Screenshot: The Invites settings page with the Create invite button and the Invite link panel showing a link and a Copy button (Create and copy an invite link)] The Invites list shows each link's code, how many times it was used and when it expires. A link created here has no use limit and never expires, so share it only with people you want in the Server. ## What the invited person does 1. They open the invite link. It shows **Join server** and your Server's name. 2. If they are not signed in, they select **Sign in to join**, then **Create account** to make an Account on your Server. 3. After signing in, they select **Join**. They can also paste the invite link into the Shell's **Connect** tab to save the Server on their device. See [Connect to a Server](https://llll.gg/docs/get-started/connect-a-server.md). > [!NOTE] > A Server starts with registration set to **Invite only**. The Owner can change it in **Server settings → Authentication → Registration** to **Open signup**. ## Give people the right Role People who join through an invite get the **Member** Role. By default it lets them send messages, attach files and start Live. To give someone more access: 1. Open **Server settings → Members** and select the person. 2. Choose a **Server role**. To shape what each Role can do, or give access only inside one Project, use **Server settings → Roles & Policies**. See [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md). To let people talk to Agents, their Role needs **Use agents**. See [Create your first Agent](https://llll.gg/docs/get-started/first-agent.md). ## Remove someone Open **Server settings → Members**, select the person and choose **Remove member**. You need the permission to remove members, and the Owner cannot be removed. ## Next steps - [Create your first Agent](https://llll.gg/docs/get-started/first-agent.md) - [Chat](https://llll.gg/docs/guides/chat.md) --- # Create your first Agent > Connect a model, create an Agent with a Role, let people use it and send it its first message in your L4 Server. Source: https://llll.gg/docs/get-started/first-agent An Agent is a non-human Member of your Server. It has a name, instructions, a model and one Role, and it can do only what that Role and the person asking allow. Each reply or piece of work is a **Run**. An Agent can chat with only a model. Working on code, from a Task to a draft pull request, also needs GitHub, a repository and compute; see [Repositories](https://llll.gg/docs/agents/repositories.md). ## 1. Connect a model The Owner connects model Providers. 1. Open **Server settings → Models**. 2. Select **Add provider** and choose one, for example OpenRouter, an Anthropic API key or ChatGPT. Keys are stored encrypted on your Server. 3. Save the provider and pick its models. On a Cloudflare Server, Workers AI is listed with the other providers. The Owner can turn it off or choose which of its models Agents see. Members with **Use agents** can also connect their own ChatGPT plan in **Server settings → Models** and share it with the Server. See [Models and Providers](https://llll.gg/docs/agents/models-and-providers.md). ## 2. Create the Agent You need **Manage agents**. The Owner has it. 1. Open **Server settings → Agents**, or the **Agents** page, and select **Create agent**. 2. Enter a **Name**. No other Member can use the same name. 3. Choose a **Model**, or leave it empty to use the Server default when one is set. 4. Choose its **Role**. The Role caps what the Agent can do. The Owner Role cannot be assigned to an Agent. 5. Write **Profile instructions**, or use **Fill instructions from an example**. 6. Select **Create agent**. [Screenshot: The Create agent dialog with Avatar, Name, Works on, Model, Role and Profile instructions fields and the Create agent button (Create an Agent)] To create an Agent that runs on its own computer instead of the Server's models, turn on **Its own computer (external agent)** under **Works on**. See [External agents](https://llll.gg/docs/agents/external-agents.md). ## 3. Let people use it People can find and talk to an Agent only if their Role, or a Project policy, grants **Use agents**. The built-in Member Role does not include it by default. 1. Open **Server settings → Roles & Policies**. 2. Open the Role whose Members should use Agents, then its **Policies** tab. 3. Check a policy that grants **Use agents**, or create one first, and select **Save policies**. ## 4. Send it a message - Open a Direct Message with the Agent from the **Agents** page, or - mention it directly in a conversation it can access, for example `@Helper summarize this thread`. Only a direct mention addresses an Agent. Role mentions and `@channel` or `@here` never wake one. Agent replies go into a Thread. ## 5. Follow its work The **Runs** page lists the Runs you started. Each Run shows its activity, model requests and result. Your Inbox shows your Agent work while it is queued or running, and approvals waiting on you. > [!NOTE] > Sending external email from an Agent always needs human approval. ## Next steps - [Agents overview](https://llll.gg/docs/agents/overview.md) - [Agents, Tasks and Runs](https://llll.gg/docs/concepts/agents-tasks-runs.md) - [MCP](https://llll.gg/docs/agents/mcp.md) --- # L4 on Cloudflare > What the L4 installer creates in your Cloudflare account, how the resources are named, and which settings manage them afterwards. Source: https://llll.gg/docs/cloudflare/overview A Cloudflare Server runs entirely in your own Cloudflare account. The official installer at [llll.gg/create](https://llll.gg/create?tab=cloudflare) creates its resources through your Cloudflare authorization and uploads a published, checksum-verified release. There is no L4 service in between: Cloudflare bills your account directly, and the Server owns its Accounts, files and conversations. To install one, follow [Create a Server on Cloudflare](https://llll.gg/docs/get-started/create-on-cloudflare.md). ## What the installer creates The installer generates a UUIDv4 for the new Server and uses it in every resource name. The same UUID is the Server's identity. | Resource | Name | Purpose | | --- | --- | --- | | Worker | `llll-` | Runs the Server and serves its Client, with static assets. | | D1 database | `llll--db` | Accounts, Members, Roles, messages, Tasks, settings and the file index. | | Workers KV namespace | `llll--tickets` | Short-lived connection tickets. Also holds files when the Server uses basic file storage. | | R2 bucket (files) | `llll--files` | Private live files. Created when R2 is enabled. | | R2 bucket (backups) | `llll--backups` | Backups, kept separate from live files. Created when R2 is enabled. | | Durable Objects | Namespaces in the Worker | Realtime channels, presence, rate limits, notifications, Agents, Runs and Schedules. | | `workers.dev` address | `llll-..workers.dev` | The Server's first address. | | Cron trigger | Weekly | Weekly backup and renewal of kept Cloudflare access. | The Worker also gets bindings for Workers AI and Browser Rendering. Cloudflare bills those products by usage, for example when an Agent uses a Workers AI model; see [Costs and limits](https://llll.gg/docs/cloudflare/costs-and-limits.md). The installer creates no custom domain, DNS record or mail configuration. You add those later from the Server's Settings. See [Custom domain](https://llll.gg/docs/cloudflare/custom-domain.md) and [Mail](https://llll.gg/docs/cloudflare/mail.md). ### Without R2 Before it allocates anything, the installer checks whether R2 is enabled on the account. If it is not, the Server uses **basic file storage**: files are stored in the KV namespace with a limit of 20 MiB per file and 500 MiB in total, and there is no separate backups bucket, so backups download to your device. You can add R2 later in **Server settings → Storage → Connect R2**, then move existing files with **Move files to R2**. The copy is verified, and the original KV copies are kept. ## What the Server keeps After installation the Worker holds only a few settings: its primary address, a random `AUTH_SECRET`, the authentication mode, the Cloudflare account ID and its resource bindings. The Server name, registration mode and mail settings live in D1. Changing the Server name in Settings never touches Cloudflare resources. The Owner setup claim is stored only as a digest in D1 and is deleted when the Owner claims the Server. If you leave **Keep Cloudflare access on this Server** on (the default), the Server also stores a Cloudflare credential, encrypted with its `AUTH_SECRET`. See [Cloudflare access](https://llll.gg/docs/cloudflare/access.md). ## Where to manage it In the Server, the Owner finds the Cloudflare deployment under **Server settings → Deployment**: | Tab | What it does | | --- | --- | | **Overview** | Shows the Server address and the Worker name. | | **Domain** | Server addresses: add, make primary, remove. See [Custom domain](https://llll.gg/docs/cloudflare/custom-domain.md). | | **Backups** | Download a backup or save one to an S3-compatible bucket. See [Backups](https://llll.gg/docs/cloudflare/backups.md). | | **Updates** | Cloudflare access and updates. See [Updates](https://llll.gg/docs/cloudflare/updates.md). | | **Remove** | How to remove the Server. See [Remove a Server](https://llll.gg/docs/cloudflare/remove-a-server.md). | File storage is under **Server settings → Storage**, and mail domains under **Server settings → Mail**. [Screenshot: The Deployment settings with the Overview, Domain, Backups, Updates and Remove tabs, showing the Server address and Runtime (Server settings → Deployment on a Cloudflare Server)] ## One Server per Cloudflare account You can create several Servers in one account; the installer lists every L4 Server it finds there under **Your Servers**. For safety, we recommend giving each Server its own Cloudflare account. A free account is enough. A credential kept on one Server then reaches only that Server's resources. This is a recommendation, not a requirement. ## Related - [Cloudflare access](https://llll.gg/docs/cloudflare/access.md) - [Costs and limits](https://llll.gg/docs/cloudflare/costs-and-limits.md) - [Updates](https://llll.gg/docs/cloudflare/updates.md) --- # Cloudflare access > How L4 uses your Cloudflare authorization, what the OAuth permissions are for, and how to keep, replace or revoke the access a Server keeps. Source: https://llll.gg/docs/cloudflare/access L4 never receives an account-wide Cloudflare credential that you did not approve. The installer works through your own Cloudflare OAuth authorization in your browser. After installation, the Server can keep Cloudflare access so it can update and reconfigure itself. Keeping it is on by default and you can turn it off at any time. ## The installer's OAuth grant When you select **Connect Cloudflare** at [llll.gg/create](https://llll.gg/create?tab=cloudflare), Cloudflare asks you to approve L4's permissions. The resulting grant: - is encrypted inside your browser-bound installer session on llll.gg; - lasts for that session only, which is one hour. Only an explicit connection renews it; polling and progress do not; - is used to list the L4 Servers in the account, create Servers, and run updates or address changes you start; - is removed when you select **Disconnect** or the session expires. If an older authorization lacks a newer permission, select **Reconnect Cloudflare** and approve again. Reconnecting keeps an unfinished installation's Server ID, Worker name and Owner claim. ### Requested permissions Cloudflare shows these on its consent screen. They fall into three groups. | Group | Permissions | Used for | | --- | --- | --- | | Provisioning | Account settings and memberships (read); Workers Scripts (admin, content read); D1 (read, write); Workers KV Storage (read, write); Workers R2 (read, write) | Finding your accounts, creating and updating the Worker, database, KV namespace and buckets. | | Domains and mail | Zone (read); DNS (read, write); Email Routing rules (read, write); Email Sending (read, write) | Server addresses and mail domains. | | Optional providers | Browser Rendering (read); Containers (read, write); Artifacts (read, write); Realtime (read, write); Queues (read, write) | Compute nodes, Live and related features you may enable later. | The grant also includes offline access, so it can be refreshed while it is in use. ## Keep Cloudflare access on this Server The installer offers **Keep Cloudflare access on this Server** when it installs or updates a Server. It is checked by default. When it is on, after the new Server answers, the installer hands its OAuth grant to the Server once through a single-use capability and then forgets it. The Server checks that the grant reaches its own Cloudflare account and stores it encrypted with its `AUTH_SECRET`. If the handoff fails, the installation still succeeds, and you can connect access later in Settings. With kept access, the Server can do these on its own, without the installer: - **Updates.** **Back up and update** runs on the Server; you can close the page. See [Updates](https://llll.gg/docs/cloudflare/updates.md). - **Server addresses.** Add, promote and remove custom domains. See [Custom domain](https://llll.gg/docs/cloudflare/custom-domain.md). - **Mail domains.** Connect and disconnect mail domains. See [Mail](https://llll.gg/docs/cloudflare/mail.md). - **Live.** Connect Live without a separate RealtimeKit token. Moving files to R2 and setting up a compute node still go through the official installer. The Server renews a kept OAuth grant every week, together with its weekly backup, so it keeps working between updates. If renewal fails, Settings asks you to reconnect. > [!TIP] > For safety, run each Server in its own Cloudflare account. A free account is enough. Kept access then reaches only that Server's resources. This is a recommendation, not a requirement. ## See and manage the kept access Only the Owner can connect or disconnect it. 1. Open **Server settings → Deployment → Updates**. 2. The **Cloudflare access** card shows **Connected** or **Not connected**, the kind (**Kept from the official installer** or **API token**) and since when. It never shows the credential. [Screenshot: The Cloudflare access card in Server settings → Deployment → Updates, showing Connected, Kept from the official installer with a date, and a Disconnect button (The Cloudflare access card)] ### Turn it off Select **Disconnect** on the **Cloudflare access** card. This deletes the credential from the Server. From then on, updates and address changes go through the official installer with a fresh, one-time authorization. An OAuth grant also stays revocable in your Cloudflare account. To be sure it can no longer be used, revoke L4's authorization in your Cloudflare profile as well. To stop the Server from receiving access during installation or a later update, uncheck **Keep Cloudflare access on this Server** before you start. ## Connect with an API token instead If you do not want to hand over the installer's grant, you can give the Server an API token you create yourself. 1. Open **Server settings → Deployment → Updates**. 2. On the **Cloudflare access** card, select **Connect with an API token**. 3. Select **Create a token in Cloudflare**. Cloudflare opens with the token name `L4 Server updates` and these permissions filled in: - Workers Scripts: Edit - D1: Edit - Workers R2 Storage: Edit - Workers KV Storage: Edit - Account Settings: Read 4. Select **Create** in Cloudflare and copy the token. 5. Paste it into the **Cloudflare API token** field and select **Connect**. The Server checks that the token reaches the account it runs in and refuses a token for another account. > [!NOTE] > The pre-filled token covers updates. It has no zone, DNS, Email Routing or Email Sending permissions, so adding addresses or mail domains from Settings needs a token with those permissions too, or the installer's grant. To revoke a token, select **Disconnect** on the card and delete the token in your Cloudflare dashboard. ## Without kept access Nothing breaks. Without kept access: - updates continue on llll.gg with a fresh Cloudflare authorization for that one update; - an address change opens the official installer, which applies it with a one-time authorization. The Server receives only a single-use request, never an account-wide credential; - mail domains are managed by the Server operator. --- # Costs and limits > Which Cloudflare products an L4 Server uses, who pays for them, and the limits that apply on Cloudflare. Source: https://llll.gg/docs/cloudflare/costs-and-limits L4 itself is free, MIT licensed software and has no hosted plan or billing. A Cloudflare Server runs in your own account, and Cloudflare bills that account directly for what the Server uses. L4 does not quote prices; check Cloudflare's current pricing for each product below. ## Products a Server uses | Product | When | Cloudflare docs | | --- | --- | --- | | Workers, with static assets and a weekly cron trigger | Always | [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/) | | D1 | Always | [D1 pricing](https://developers.cloudflare.com/d1/platform/pricing/) | | Durable Objects | Always: realtime, presence, notifications, Agents, Runs, Schedules | [Durable Objects pricing](https://developers.cloudflare.com/durable-objects/platform/pricing/) | | Workers KV | Always: connection tickets, and files with basic storage | [KV pricing](https://developers.cloudflare.com/kv/platform/pricing/) | | R2 | When R2 is enabled: the files bucket and the separate backups bucket | [R2 pricing](https://developers.cloudflare.com/r2/pricing/) | | Workers AI | When an Agent or AI function uses a Workers AI model | [Workers AI pricing](https://developers.cloudflare.com/workers-ai/platform/pricing/) | | Browser Rendering | When a feature reads a web page through it | [Browser Rendering](https://developers.cloudflare.com/browser-rendering/) | | Email Routing and Email Sending | When you connect a mail domain | [Email Routing](https://developers.cloudflare.com/email-routing/) | | Realtime (RealtimeKit) | When you connect Live | [Realtime](https://developers.cloudflare.com/realtime/) | | Containers and Artifacts | When you enable a compute node for Agents | [Containers](https://developers.cloudflare.com/containers/) | Cloudflare bills the optional products by usage, so they matter only once you set them up and use them. A compute node uses paid Containers and Artifacts; the installer asks you to confirm those charges before it provisions one. Model Providers you connect, such as OpenRouter, Anthropic or ChatGPT, bill you separately. ### Backups take storage With R2, the Server writes a backup to its backups bucket every week and before each update. L4 does not delete old backups, so the backups bucket grows over time. Delete archives you no longer need in the Cloudflare dashboard, and keep at least one recent one. See [Backups](https://llll.gg/docs/cloudflare/backups.md). ## Free and paid Workers plans A Server can start on a free Cloudflare account. Some limits are tighter on the Workers Free plan: - A self-update from Settings runs in one Durable Object alarm. On Workers Free its subrequest budget is tight for releases with large asset changes. If an update stops, use **Retry update** or run the update through the official installer. - Cloudflare's daily and per-request quotas for Workers, D1, KV and Durable Objects apply to the whole account. If you run several Servers in one account, they share those quotas. See [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) for the current numbers. ## L4 limits on Cloudflare | Limit | Value | | --- | --- | | Basic file storage (no R2) | 20 MiB per file, 500 MiB in total. KV's own account limits and daily quotas also apply. | | Mail attachments | Up to 10 attachments, 3 MB in total, per email. | | Server name | Up to 80 characters. | | Installer session | One hour, renewed only by connecting Cloudflare again. | ## Keep costs predictable - Give each Server its own Cloudflare account. Usage and quotas then stay separate, and kept access reaches only that Server. A free account is enough. - Enable optional products only when you need them. - Set usage notifications in your Cloudflare account. - Prune old backups in the backups bucket. --- # Custom domain > Give your Cloudflare L4 Server its own domain, choose the primary address, and add or remove aliases without breaking existing links. Source: https://llll.gg/docs/cloudflare/custom-domain A new Cloudflare Server starts on its `workers.dev` address. You can add your own domains at any time. A Server can answer at several addresses: - The **primary address** is used for new links, the Shell's display and background email. - **Aliases** are additional addresses. They reach the same Server, with the same UUID, Accounts and content. Every listed address works for login, sessions, Shell embedding, links, uploads, MCP and realtime. Adding an address never disables the others, and the `workers.dev` address always stays enabled. ## Before you start - The hostname must be in an **active zone in the same Cloudflare account** as the Server. Add the domain to Cloudflare first if it is not there yet. - The hostname must not already be attached to another Worker. - You must be the Owner. ## Add an address 1. In your Server, open **Server settings → Deployment → Domain**. The page is titled **Server address**. 2. Select **Add address**. 3. In **Domain**, enter the hostname, for example `chat.example.com`. 4. Optional: check **Make this the primary address**. The current primary stays available as an alias. 5. Confirm: - If the Server keeps Cloudflare access, select **Add address**. The Server attaches the domain itself. - Otherwise, select **Continue to Cloudflare**. The official installer opens on llll.gg; select **Connect Cloudflare**, sign in to the account that owns the Server, then select **Apply the change**. Keep the window open until it finishes, then select **Return to Server settings**. [Screenshot: The Server address settings with the Addresses list showing a workers.dev address marked Always available, a custom domain marked Primary, and the Add address form with the Domain field (Server settings → Deployment → Domain)] The installer attaches the hostname as a Worker custom domain in your zone. Cloudflare creates the DNS record and certificate for it. The **Requests** list on the same page shows each change and whether it completed or failed, with the reason. ## Change the primary address On an alias, select **Make primary**. The old primary stays as an alias, so existing links keep working. New links, Shell bookmarks and background email then use the new primary. People who saved the Server in their Shell under an older address keep reaching it; the Shell recognizes the Server by its UUID. ## Remove an alias On an alias, select **Remove**. This detaches the custom domain from the Worker. You cannot remove the primary address. Make another address primary first. The `workers.dev` address is marked **Always available** and cannot be removed here. > [!WARNING] > Links, bookmarks and MCP clients that use a removed address stop working. Make sure people use the primary address before you remove an old one. ## Without kept Cloudflare access If the Server does not keep Cloudflare access, every add, promote or remove goes through the official installer. The Server creates a single-use request; the installer applies it with your one-time Cloudflare authorization. The Server never receives an account-wide credential this way. See [Cloudflare access](https://llll.gg/docs/cloudflare/access.md). ## Things to check - **Only listed addresses are trusted.** A hostname that merely reaches the Worker, without being listed, is rejected for sessions and changes. It is never added automatically. - **External sign-in providers.** If you use GitHub, Google or another login provider in **Server settings → Authentication**, allow its callback URL on each address you enable there. - **Zone features that rewrite scripts.** Features that inject or rewrite inline scripts, such as Rocket Loader, are incompatible with the Server's Content-Security-Policy. Keep them off for the Server's hostnames. ## Related - [Mail](https://llll.gg/docs/cloudflare/mail.md) - [Server and Shell](https://llll.gg/docs/concepts/server-and-shell.md) --- # Mail on Cloudflare > Connect a mail domain to your Cloudflare L4 Server with Cloudflare Email Routing and Email Sending, then create shared Mailboxes. Source: https://llll.gg/docs/cloudflare/mail Mail is optional. A Cloudflare Server receives and sends email for its shared Mailboxes through Cloudflare **Email Routing** and **Email Sending** on a domain in your Cloudflare account. The installer does not set up mail; you connect a mail domain later from the Server's Settings. For using Mail day to day, see the [Mail guide](https://llll.gg/docs/guides/mail.md). ## Before you start - The Server must **keep Cloudflare access** with permission for zones, DNS, Email Routing and Email Sending. The installer's OAuth grant has these. The pre-filled API token for updates does not. See [Cloudflare access](https://llll.gg/docs/cloudflare/access.md). - The domain must be in an active zone in the same Cloudflare account as the Server. - You must be the Owner. Without kept access, the Domains tab says email domains are managed by the Server operator. > [!WARNING] > Connecting a domain turns on Cloudflare Email Routing for it and routes its mail to this Server. If the domain already receives mail through another provider, use a subdomain such as `mail.example.com` instead, so existing mail keeps working. ## Connect a mail domain 1. In your Server, open **Server settings → Mail** and choose the **Domains** tab. 2. Select **Connect domain**. 3. Choose the **Cloudflare zone**. 4. In **Mail domain**, enter the zone itself or a subdomain, for example `mail.example.com`. 5. Select **Connect**. [Screenshot: The Domains tab in Mail settings with the Connect domain form showing the Cloudflare zone select, the Mail domain field and the Connect button (Connect a mail domain)] The Server then: - enables Cloudflare Email Routing for the domain; - onboards the domain for Cloudflare Email Sending, or re-enables it if it was turned off; - adds the Email Sending binding to its Worker. If the page shows **Sending is not set up yet**, mail can be received but the Server cannot send until the domain finishes connecting in Cloudflare. Check the domain's status under Email in your Cloudflare dashboard. ## Create a Mailbox 1. In **Server settings → Mail**, choose the **Mailboxes** tab. 2. Select **New mailbox**. 3. Enter the address and choose the domain, then select **Create mailbox**. 4. Open the Mailbox to set its **Display name** and choose who has access. Each Mailbox has its own access rules. People need a grant on the Mailbox plus **Read email** or **Send email**. ## Disconnect a mail domain On the **Domains** tab, select **Disconnect** next to the domain. - If L4 created the domain's Email Sending configuration, it removes it again. - When no mail domain remains, the Email Sending binding is removed from the Worker. - Mailboxes and their history stay on the Server. ## Limits - Up to 10 attachments, 3 MB in total, per email. This keeps a message within Cloudflare Email Sending's 5 MiB limit once encoded. - Sending external email from an Agent always needs human approval. Cloudflare's own Email Routing and Email Sending limits and pricing apply. See [Email Routing](https://developers.cloudflare.com/email-routing/) in the Cloudflare docs. ## Related - [Mail guide](https://llll.gg/docs/guides/mail.md) - [Custom domain](https://llll.gg/docs/cloudflare/custom-domain.md) --- # Update a Cloudflare Server > Update your Cloudflare L4 Server to a newer published release from its Settings, with a verified backup first, with or without the installer. Source: https://llll.gg/docs/cloudflare/updates A Cloudflare Server updates only when its Owner chooses to. Every update installs a published, checksum-verified release and makes a verified backup first. You never need Git, GitHub or Workers Builds. There are two ways an update runs: - **On the Server itself**, when the Server keeps Cloudflare access. You can close the page while it runs. - **Through the official installer** on llll.gg, with a fresh Cloudflare authorization for that one update. ## Start an update 1. In your Server, open **Server settings → Deployment → Updates**. The Deployment entry shows a badge when newer releases exist. 2. Check **Installed version**. 3. Choose a **Release**. Only published releases newer than the installed version can be selected. Check **Include prereleases** to also list alpha, beta and rc releases. 4. Read the release notes shown below the selector. 5. Select **Back up and update**. [Screenshot: The Updates settings with the Cloudflare access card, the Installed version, the Release selector with release notes, and the Back up and update button (Server settings → Deployment → Updates)] What happens next depends on whether the Server keeps Cloudflare access. ## When the Server keeps Cloudflare access The request is saved on the Server. Select **Start update**. The Server then: 1. verifies the release against its published checksums; 2. writes a backup to its separate backups bucket and reads it back to check its size and SHA-256; 3. uploads the release; 4. lets the new version confirm its own version and migrations. You can close the page. Each stage is recorded before it starts. A failure before the upload stops with nothing changed. A failure after it asks for attention; select **Retry update** to resume after the verified backup. This path needs the separate backups bucket. A Server with basic file storage uses the installer path with a downloaded backup. ## Through the official installer The request is saved on the Server. Select **Continue on llll.gg**. The installer opens a page titled **Update a Server**. 1. Connect Cloudflare and approve the permissions. The grant stays in your browser's installer session. 2. Optional: leave **Keep Cloudflare access on this Server** checked so later updates run from Settings. 3. Select **Back up and update**. The installer shows each phase: **Verify the published release**, **Back up and verify Server data**, **Upload the new release**, **Wait for Cloudflare verification** and **Update complete**. Each phase is saved, so reloading or closing the page loses nothing. Reopen the update from Settings to continue. The installer's backup check is thorough. It reads the stored archive back from R2 and compares its hash, size, every entry and the database and file inventories with the live Server. If the Server changed while the backup was made, the update stops before deployment and asks you to retry with low activity. When it finishes, select **Open your Server**. ### Without a backups bucket If the Server uses basic file storage and has no separate backups bucket, Settings shows **Download backup** first. Your browser downloads the complete archive and checks it before the update may continue. Keep that file for recovery. ## After the update If your browser still shows the old interface, Settings offers a button to load the new one. The Server's UUID, Accounts, bindings, secrets and storage are kept. ## If something goes wrong - **Cancel request** removes a queued update before it starts. - If the backup failed before deployment, retry; an unverified archive is discarded and a new backup is made. - If the browser session was lost, reopen the update from Settings. A different browser can take over an active update only after its last step is more than 20 minutes old and no backup is still being written. - A failure after deployment began shows **Deployment needs verification**. Reopening the update rechecks the Server and reruns the upload only if the new version is not installed yet. There is no automatic downgrade or data rollback. The verified backup stays in the separate bucket for recovery. See [Backups](https://llll.gg/docs/cloudflare/backups.md). ## Release lineage A Server updates only within its schema lineage. Servers on 0.3.x use `server-v2`. A 0.2.x Server cannot be updated to 0.3.0 or later; install a new Server instead. See [Releases and updates](https://llll.gg/docs/concepts/releases-and-updates.md). --- # Backups on Cloudflare > How a Cloudflare L4 Server backs up its database and files, where backups are kept, and how to make one yourself. Source: https://llll.gg/docs/cloudflare/backups A Backup is a recovery copy of your Server, stored separately from its live files. It is not a second running Server. L4 never deletes backup buckets together with a Server. ## Automatic backups When R2 is enabled, the installer creates a separate backups bucket named `llll--backups`, next to the files bucket `llll--files`. The Server then writes a backup there: - **every week**, from its weekly cron trigger; - **before every update**, verified before the new release is uploaded. See [Updates](https://llll.gg/docs/cloudflare/updates.md). Archives are stored under `servers//` in the bucket, named by the time they were made. L4 does not delete old archives. Prune them yourself in the Cloudflare dashboard when you no longer need them, and keep at least one recent one. In **Server settings → Deployment → Backups**, this bucket appears as **Separate backup bucket provided by this deployment**. A Server with basic file storage has no backups bucket and makes no automatic backups. Download backups yourself, or connect R2 in **Server settings → Storage**. ## What a backup contains Each backup is a TAR archive with: - a logical export of the D1 database: Accounts, Members, Roles, messages, settings and the rest of the Server's data; - every stored file, from R2 or from basic KV storage. It does **not** contain: - Worker environment secrets, including `AUTH_SECRET`; - live Durable Object state; - KV connection tickets; - data held by external Providers, such as RealtimeKit. > [!IMPORTANT] > Settings saved on the Server, such as provider keys and kept Cloudflare access, are encrypted with `AUTH_SECRET`, which is not in the archive. The installer generates it as a secret on the Server's Worker, and Cloudflare does not display secret values. Data restored without the original secret cannot decrypt those settings; they must be entered again. ## Download a backup 1. Open **Server settings → Deployment → Backups**. 2. Under **Download to this device**, select **Download backup**. The Worker reads the database and every stored file while the download runs. This can take a long time. Keep the tab open, and keep the Server idle until the download finishes. ## Save a backup to another bucket You can also send backups to any S3-compatible service: Cloudflare R2, AWS S3, Backblaze B2, Wasabi, MinIO and others. 1. Open **Server settings → Deployment → Backups**. 2. Under **Save to S3-compatible bucket**, enter the **Endpoint**, **Region** (use `auto` for Cloudflare R2), **Bucket**, optional **Prefix**, **Access Key ID** and **Secret Access Key**. 3. Select **Test connection**, then **Save credentials**. 4. Select **Backup to bucket**. Keep the tab open; large Servers can take several minutes. [Screenshot: The Backups settings with Download to this device and Save to S3-compatible bucket sections, the Endpoint, Region, Bucket, Prefix and key fields, and the Backup to bucket button (Server settings → Deployment → Backups)] > [!WARNING] > Use a separate private bucket. Do not use the R2 bucket already bound to L4 for live files. Otherwise later archives can include earlier backups, and one bucket failure can remove both the files and their backups. A bucket chosen here is for manual backups. It does not replace the backups bucket used by updates. ## Backups and removal Backups are never deleted with a Server. When you [remove a Server](https://llll.gg/docs/cloudflare/remove-a-server.md), keep its backups bucket and any downloaded archives until you are sure you no longer need them. ## Restoring Restore means bringing the Server's data back into new, isolated resources and checking that the same UUID, Accounts, Roles, messages and files are there. There is no restore button in Settings, and updates never roll data back automatically. If you might need to recover a Server, keep its archives and do not delete its original Worker, which holds `AUTH_SECRET`. See [Files and backups](https://llll.gg/docs/concepts/files-and-backups.md). --- # Remove a Cloudflare Server > Remove an L4 Server from your Cloudflare account safely, keeping its backups, and clean up domains, mail and kept access. Source: https://llll.gg/docs/cloudflare/remove-a-server L4 does not delete Servers for you. You remove a Cloudflare Server's resources yourself in your Cloudflare account, after saving a backup. Backups are never deleted with a Server; keep them until you are sure you no longer need them. > [!WARNING] > Removing a Server permanently deletes its Accounts, conversations, files and settings for everyone. Members lose access. Make and keep a backup first. ## 1. Find the Server's resources In your Server, open **Server settings → Deployment → Overview**. **Runtime** shows the Worker name, `llll-`. Every resource of this Server uses the same UUID: | Resource | Name | Remove? | | --- | --- | --- | | Worker | `llll-` | Yes | | D1 database | `llll--db` | Yes | | KV namespace | `llll--tickets` | Yes | | R2 files bucket | `llll--files` | Yes, if it exists | | R2 backups bucket | `llll--backups` | **No. Keep it.** | A Server created by an older release may use a different Worker name; the Overview tab always shows the real one. ## 2. Make a backup 1. Open **Server settings → Deployment → Remove** and select **Review backups**, or go to **Server settings → Deployment → Backups**. 2. Select **Download backup**, or save one to a separate S3-compatible bucket. 3. Keep the archive somewhere safe. See [Backups](https://llll.gg/docs/cloudflare/backups.md). [Screenshot: The Remove server tab with the notice that removal is handled by the deployment operator and the Review backups button (Server settings → Deployment → Remove)] ## 3. Clean up while the Server still runs If the Server keeps Cloudflare access, do these from its Settings first, so it removes its own Cloudflare configuration. Otherwise, mail domains and aliases are removed through the installer or by you in Cloudflare (step 4). 1. **Mail domains.** In **Server settings → Mail → Domains**, select **Disconnect** for each domain. 2. **Aliases.** In **Server settings → Deployment → Domain**, select **Remove** on each alias. The primary address cannot be removed here; check for it in step 4. 3. **Kept Cloudflare access.** In **Server settings → Deployment → Updates**, select **Disconnect** on the **Cloudflare access** card. ## 4. Delete the resources in Cloudflare In the Cloudflare dashboard for the Server's account: 1. Delete the Worker `llll-`. 2. Delete the D1 database `llll--db`. 3. Delete the KV namespace `llll--tickets`. 4. Delete the R2 bucket `llll--files`, if there is one. 5. Check your zone for anything left over for this Worker: Workers custom domains, Email Routing rules that send to it, and Email Sending configuration for its mail domains. Keep the R2 bucket `llll--backups` and any downloaded archives. > [!NOTE] > Deleting the Worker also deletes its `AUTH_SECRET`. Settings that the Server encrypted with it, such as provider keys, cannot be decrypted from a backup afterwards. ## 5. Revoke Cloudflare access - If the Server kept the installer's OAuth grant, revoke L4's authorization in your Cloudflare profile. - If you connected an API token, delete that token in the Cloudflare dashboard. ## 6. Remove the bookmark from devices Saved Servers are bookmarks on each device. When a saved `workers.dev` address no longer has a Worker, the Shell notices and removes it from that device. A Server saved under a custom domain is never removed automatically: select **Remove from this device** in `/create`, or the **×** next to it under **Saved on this device**. ## Pause instead of removing An unfinished installation you no longer want is not removed by **Pause and start another** or by disconnecting Cloudflare. Pausing keeps its resources so you can resume. Delete them in Cloudflare the same way, and keep any backups. --- # Run a Server on a VPS > Operator reference for the L4 VPS installer and the llll command, from install and addresses to backups, upgrades, restore and uninstall. Source: https://llll.gg/docs/self-host/vps The VPS installer runs one standalone L4 Server on a Linux machine you control. It uses Docker Compose to run the Server on a single [celld](https://celld.dev) node, a private [RustFS](https://github.com/rustfs/rustfs) bucket for storage and Caddy for public HTTPS. The Server's Accounts, UUID, files and realtime all stay on that machine. This page is the operator reference. For a short first install, see [Install on a VPS](https://llll.gg/docs/get-started/install-on-a-vps.md). > [!NOTE] > The Cloudflare path is verified first. The VPS path is newer: fresh-VM HTTPS and browser acceptance are still being recorded. Test your Server (claim, invite, chat, files) before you invite many people. ## Requirements - A dedicated **amd64** VPS running Debian 12 or 13, or Ubuntu 22.04 or 24.04. VPS releases support Linux amd64 only. - At least 2 GiB RAM and 5 GiB free disk. - A domain whose A/AAAA records point at the VPS. A domain is required; there is no automatic IP-based hostname. - Inbound TCP 80 and 443 open. UDP 443 is optional (HTTP/3). - `bash`, `curl`, CA certificates and the standard core utilities. The script installs missing `jq`, OpenSSL and iproute2. If Docker is missing, it installs Docker Engine and Compose from Docker's official apt repository. An existing Docker must be Engine 24 or newer with Compose 2.24.4 or newer; it is never replaced. Snap Docker and remote Docker daemons are refused. Caddy gets and renews certificates itself. Its [HTTPS requirements](https://caddyserver.com/docs/automatic-https) need correct DNS and reachable ports. If the VPS already runs another reverse proxy, use [Docker Compose](https://llll.gg/docs/self-host/docker.md) instead. ## Install Run as root on the VPS: ```bash curl -fsSL https://llll.gg/install.sh | sudo env \ LLLL_DOMAIN=chat.example.com \ LLLL_ADMIN_EMAIL=owner@example.com \ bash ``` The **VPS** tab on the [Create page](https://llll.gg/create?tab=vps) builds the same command from your domain and Owner email. The downloaded script pins its exact version, Git commit and the SHA-256 of the runtime image and Compose files. It never builds source on the VPS. A checksum failure stops the script before it installs packages, loads an image or writes any installation files. When it finishes, the installer waits for the Server to be healthy and checks its public address, version and UUID. If the Server has no Owner yet, it prints a private link ending in `/setup#claim=…`. Open it, choose the Owner password, then invite a member. > [!WARNING] > The setup link is printed only to your terminal. It is not written to a log or a file. Do not paste terminal output into shared logs. ### Inspect the script first To download and check a named release before running it: ```bash version='' base="https://github.com/inchidev/llll/releases/download/v${version}" curl -fSLO "$base/install.sh" curl -fSLO "$base/SHA256SUMS" awk '$2 == "install.sh"' SHA256SUMS | sha256sum --check less install.sh sudo env LLLL_DOMAIN=chat.example.com LLLL_ADMIN_EMAIL=owner@example.com bash install.sh ``` For a prerelease (alpha, beta or rc), add `LLLL_CHANNEL=prerelease`. Checksums and provenance come from GitHub over HTTPS; releases are not cryptographically signed. ### What the installer creates - `/opt/llll`, mode 0700, with the private `.env` (mode 0600). - A private copy of the CLI and `/usr/local/bin/llll`. - A generated `SERVER_ID` UUIDv4 in `.env`. It names the Server, its resources and its backups. Running the same installer again restarts and verifies the Server without changing `.env`, credentials, volumes or UUID. To move to another version, use `llll upgrade`. If an install fails, the private configuration is kept so you can fix DNS, firewall or settings and rerun the same script. ## Everyday commands ```bash sudo llll status sudo llll logs sudo llll stop sudo llll start sudo llll backup sudo llll upgrade ``` `status` shows the containers, version and primary address. `logs` follows the last 100 lines. ## Server addresses A Server can answer at several addresses. One is the **primary address**; the others are **aliases**. All of them reach the same Server, Accounts and content. ```bash sudo llll domain list sudo llll domain add chat.example.com sudo llll domain primary chat.example.com sudo llll domain remove old.example.com ``` To add an address: 1. Point the new hostname's DNS at the VPS. 2. Run `sudo llll domain add `. The command adds the origin to `LLLL_ORIGINS`, adds the name to Caddy, restarts the stack, waits for a certificate and checks discovery at every address. If any check fails, it restores the previous `.env` and configuration. `domain primary` changes the primary address (`PUBLIC_ORIGIN`) and keeps the old one as an alias. You cannot remove the primary address; promote another one first. ## Backups ```bash sudo llll backup ``` A backup stops the stack for consistency, archives `.env`, the deployment and CLI files, and the RustFS, celld and Caddy volumes, then starts the stack again (also when the archive fails). Archives are mode 0600 under `/var/backups/llll/`. The latest seven are kept; set `LLLL_BACKUP_KEEP` on first install to change this. > [!IMPORTANT] > celld stores the Server's Worker variables, including `AUTH_SECRET`, in plaintext inside its private bucket. Treat the VPS disk and every backup archive as secrets. Copy backups off the host, encrypted, and test a restore. Scheduled backups are not supported yet. Run `llll backup` yourself or from your own scheduler. ## Upgrade ```bash sudo llll upgrade # latest stable release sudo env LLLL_CHANNEL=prerelease llll upgrade # a named prerelease ``` An upgrade: 1. Downloads the selected release's installer and verifies it. 2. Refuses a downgrade. 3. Verifies and loads the new runtime image. 4. Takes a mandatory backup. 5. Starts the new version and checks it. If startup or discovery fails, it stops the failed stack, restores the previous Compose files, `.env`, CLI and all four volumes, and checks the old version again. The old image stays on the machine. Expect a short outage, because a single node restarts. If the rollback itself fails, the backup is kept and the command fails; read `llll logs` before you change anything else. Automatic updates are not supported yet. ## Restore Restore onto a fresh host that uses the same DNS name, with a published `install.sh`: ```bash sudo env LLLL_DIR=/opt/llll bash install.sh restore /private/llll-backup-….tar.gz ``` The target directory must not exist and the four volumes must be empty. Restore verifies the archive, downloads and checks the runtime version that made the backup, restores the original secrets and UUID, and checks discovery. Point DNS at the restored host. Check that existing Accounts, sessions and private files work before switching traffic. > [!WARNING] > Never run two active copies of the same Server at once. ## Uninstall ```bash sudo llll uninstall # confirms the project and backs up first sudo llll uninstall --purge # also removes this Server's live volumes ``` `--yes` confirms without a prompt; `--no-backup` explicitly skips the backup. Uninstall keeps the configuration, the CLI, all backup archives, Docker and shared images. It never prunes Docker. After a plain uninstall, `llll start` brings the Server back. After `--purge`, restore from a backup. ## Configuration Inputs apply on first install. Reruns keep the saved values. | Input | Default and meaning | | --- | --- | | `LLLL_DOMAIN`, `LLLL_ADMIN_EMAIL` | Required on first install. Domain without scheme or path; email of the first Owner. | | `LLLL_ADMIN_PASSWORD` | Optional, at least 12 characters. Creates the first Owner with this password, so no claim link is printed. It is stored in `.env`; change it after signing in. | | `PUBLIC_ORIGIN`, `LLLL_ORIGINS` | Primary origin and comma-separated aliases. Managed by `llll domain`; do not edit by hand. | | `LLLL_DIR` | `/opt/llll`. Use the same value with every CLI command if you change it. | | `LLLL_CHANNEL` | `stable`. Set `prerelease` to install or upgrade to an alpha, beta or rc. | | `LLLL_ACME_EMAIL` | The Owner email; used for Caddy's ACME account. | | `LLLL_HTTP_PORT`, `LLLL_HTTPS_PORT` | `80` and `443`. Custom ports need public ACME challenge ports forwarded. | | `LLLL_NO_TLS` | `false`. `true` serves plain HTTP, for isolated tests only. | | `LLLL_SKIP_DOCKER_INSTALL` | `false`. `true` requires an existing local Docker Engine and Compose. | | `LLLL_BACKUP_DIR` | `/var/backups/llll/`. Must be outside `LLLL_DIR`. | | `LLLL_BACKUP_KEEP` | `7`. | | `LLLL_FACTORY`, `LLLL_SHARED_KERNEL_TRUSTED` | Set both (`true`, `1`) to add a [Compute Node](https://llll.gg/docs/self-host/compute-nodes.md) during install. | The version is chosen by the installer or `llll upgrade`, never by editing an environment variable. ### Optional providers Mail, AI models, page reading and calls are optional. To add them, edit the private `/opt/llll/.env` using the keys listed in [`self-host/.env.example`](https://github.com/inchidev/llll/blob/main/self-host/.env.example), then run `sudo llll start`. See [Providers off Cloudflare](https://llll.gg/docs/self-host/docker.md#optional-providers) for what each key does. ## Related - [Docker Compose](https://llll.gg/docs/self-host/docker.md) for a manual stack behind your own proxy. - [Compute Nodes](https://llll.gg/docs/self-host/compute-nodes.md) to let Agents work on repositories on this VPS. - [Releases and updates](https://llll.gg/docs/concepts/releases-and-updates.md). - [Troubleshooting](https://llll.gg/docs/reference/troubleshooting.md). --- # Run a Server with Docker Compose > Run one L4 Server with Docker Compose behind your own reverse proxy, then update it by changing the image tag. Source: https://llll.gg/docs/self-host/docker The common Docker Compose stack runs one standalone L4 Server on a single [celld](https://celld.dev) node, with private [RustFS](https://github.com/rustfs/rustfs) storage and a Caddy gateway. Use it when you want to manage Docker yourself, or when the machine already has a reverse proxy. For a managed install with HTTPS, backups and rollback, use the [VPS installer](https://llll.gg/docs/self-host/vps.md). For Coolify, see [Coolify](https://llll.gg/docs/self-host/coolify.md). ## What runs | Service | Role | | --- | --- | | `llll` | The Server on celld. No host port is published. | | `rustfs` | Private S3 bucket that holds the database, Durable Object storage, KV, files and the deployment. No host port is published. | | `rustfs-perms`, `bucket-init` | One-shot helpers that prepare the volume and create the bucket. | | `gateway` | Caddy. Listens on `127.0.0.1:8080` by default. | The stack uses four volumes: `rustfs-data`, `celld-data`, `caddy-data` and `caddy-config`. > [!WARNING] > celld has no secrets store. On start the container writes your `.env` into the deployment, which celld stores as plaintext in the RustFS bucket. Keep the RustFS volume private and never publish port 9000. ## Get the image Pick one: - **Published image (stable releases).** `ghcr.io/inchidev/llll:`, `:` (for example `0.3`) and `:latest`, for `linux/amd64` and `linux/arm64`. Prereleases are not published as images. - **Release archive.** Each release includes `llll-runtime-linux-amd64.tar.gz` and `SHA256SUMS`. Verify the checksum, then `docker load --input llll-runtime-linux-amd64.tar.gz`. This loads `llll:` locally. The common Compose file uses `pull_policy: never`, so pull the published image yourself before starting: ```bash docker pull ghcr.io/inchidev/llll:latest ``` ## Set up 1. Get the `self-host` folder from the release you run, for example: ```bash git clone --depth 1 --branch v https://github.com/inchidev/llll.git cd llll/self-host ``` 2. Create the private configuration: ```bash cp .env.example .env openssl rand -hex 32 # run twice: one value for AUTH_SECRET, one for SETUP_TOKEN ``` 3. Edit `.env`: | Key | Value | | --- | --- | | `PUBLIC_ORIGIN` | The origin members open, for example `https://chat.example.com`. | | `AUTH_SECRET` | Random, at least 32 characters. Signs sessions and encrypts stored credentials. | | `SETUP_TOKEN` | Random, at least 32 characters. Used once to claim the Owner. | | `ADMIN_EMAIL` | Email of the first Owner. | | `RUSTFS_ACCESS_KEY`, `RUSTFS_SECRET_KEY` | Keys for the private bucket. | | `LLLL_IMAGE` | `ghcr.io/inchidev/llll:` or `llll:`. | 4. Start the stack: ```bash docker compose up -d --wait ``` 5. Open `/setup`, enter `SETUP_TOKEN` and create the Owner. `/setup#claim=` skips the token field. If `.env` sets `ADMIN_PASSWORD` (at least 12 characters), the first start creates the Owner instead: sign in with `ADMIN_EMAIL` and that password, then change it. The `llll` container stays unhealthy until its database migrations have been applied. Caddy starts after that, and `/api/setup/health` returns HTTP 200. On first start, the log line `waiting for the celld node to join` is expected. > [!IMPORTANT] > Never change `AUTH_SECRET` after setup. It signs sessions, and credentials the Server stores (such as backup keys and provider keys) are encrypted with it. ## Reverse proxy and TLS The gateway listens on loopback HTTP. For public use, keep it on loopback and terminate TLS in front of it. Set `PUBLIC_ORIGIN` to the public HTTPS origin and run `docker compose up -d` again. Caddy on the host: ```text chat.example.com { reverse_proxy 127.0.0.1:8080 } ``` nginx must forward WebSocket upgrades for chat: ```text server { server_name chat.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-Proto https; } } ``` `LLLL_HTTP_PORT` changes the host port. `LLLL_BIND=0.0.0.0` exposes the gateway on every interface; configure TLS before you invite anyone. ### More than one address To serve extra hostnames, set `LLLL_ORIGINS` to a comma-separated list of origins and route all of them to the same container. Only listed origins are trusted. `PUBLIC_ORIGIN` stays the [primary address](https://llll.gg/docs/concepts/server-and-shell.md#addresses). ## Update Updates are an image tag. L4 Settings does not update a Docker Server; **Server settings → Deployment → Updates** says that updates are managed outside L4 and shows when a newer stable release exists. 1. Take and verify a backup (below). 2. Pull or load the new image and set `LLLL_IMAGE` to it in `.env`. 3. Run `docker compose up -d --wait`. On start, the container applies the new database migrations; ones already applied are skipped. Expect a short outage while the single node restarts. Migrations only go forward. The container records the release that last migrated its data and refuses to start an older image: ```text llll: this data was migrated by L4 0.3.4; image 0.3.3 is older. Use 0.3.4 or newer. ``` To go back, restore a backup instead. ## Back up and restore Stop the stack, then copy both data volumes and `.env`. The names below assume the Compose project `llll`: ```bash docker compose stop docker run --rm -v llll_rustfs-data:/from -v "$PWD":/to alpine:3.22 \ tar -C /from -czf /to/rustfs-data.tgz . docker run --rm -v llll_celld-data:/from -v "$PWD":/to alpine:3.22 \ tar -C /from -czf /to/celld-data.tgz . docker compose start ``` - `rustfs-data` holds the database, Durable Object storage, KV, Server files and the deployment, including plaintext Worker variables. - `celld-data` is the node's local work directory. - `.env` holds `AUTH_SECRET` and the RustFS keys. Store all three privately and outside the live volumes. To restore, stop the stack, extract the archives onto empty volumes with the same names, keep the same `.env` and start again. **Server settings → Deployment → Backups** can also download an archive of the database and files, or send it to an S3 destination. That archive does not include the celld deployment, KV or live Durable Object state. See [Files and backups](https://llll.gg/docs/concepts/files-and-backups.md). ## Optional providers Text chat, Tasks, files and backups work without any provider. Add these keys to `.env` to turn on more. Empty values are ignored. | Keys | Turns on | | --- | --- | | `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN` | Workers AI, Browser Run (page reading), Email Sending and RealtimeKit calls over Cloudflare's REST API. The account ID is 32 hex characters. | | `AI_BASE_URL`, `AI_API_KEY`, `AI_MODEL` | An OpenAI-compatible model endpoint, used when the Cloudflare token is not set. Set all three. | | `MAIL_GATEWAY_URL`, `MAIL_GATEWAY_TOKEN` or `SMTP_GATEWAY_URL`, `SMTP_GATEWAY_TOKEN` | Outbound mail through an HTTP gateway you run. The Server does not open an SMTP port. | | `INBOUND_EMAIL_SECRET`, `POSTMARK_INBOUND_SECRET`, `MAILGUN_WEBHOOK_SIGNING_KEY`, `SES_SNS_TOPIC_ARN` | Inbound mail through a signed webhook. Secrets must be at least 32 characters. | | `BROWSER_CDP_URL` | A private Chromium DevTools endpoint for page reading. Do not publish its port. | `/api/setup/health` reports which provider the Server chose. The [celld reference](https://github.com/inchidev/llll/blob/main/docs/self-host/celld.md) documents each provider, the token permissions and the inbound mail setup in detail. Without a Cloudflare token, calls stay unavailable and the call button is disabled; text chat keeps working. ## Build from source `compose.build.yml` adds a local source build: ```bash docker compose -f docker-compose.yml -f compose.build.yml up -d --build --wait ``` The build needs about 8 GB of free RAM. Use it to test source changes, not to run a community Server. --- # Run a Server on Coolify > Deploy one L4 Server in Coolify from the published image, claim the Owner, add addresses and update by redeploying or pinning a version. Source: https://llll.gg/docs/self-host/coolify The Coolify template runs one standalone L4 Server: L4 on a single celld node with private RustFS storage. Coolify's proxy owns the domain and TLS. The template uses the published `ghcr.io/inchidev/llll` image. It is public, so Coolify pulls it without a registry login and nothing is built on your machine. The template is [`self-host/coolify/docker-compose.yaml`](https://github.com/inchidev/llll/blob/main/self-host/coolify/docker-compose.yaml). It is not a published Coolify catalog entry yet. ## Install 1. In Coolify, choose **New Resource → Docker Compose** and paste the contents of `docker-compose.yaml`. 2. Set the domain of the `llll` service, on port `8080`, and `ADMIN_EMAIL` (the first Owner's email). The deployment fails without `ADMIN_EMAIL`. Leave `LLLL_VERSION` unset to run `latest`. 3. Deploy. The first start takes a few minutes while the database migrations are applied. 4. Wait until `https:///api/setup/health` answers with HTTP 200. 5. Open `https:///setup`, paste the setup token and create the Owner. The token is the value of `SERVICE_PASSWORD_64_SETUPTOKEN` in the service's environment variables in Coolify. To skip step 5, also set `ADMIN_PASSWORD` (at least 12 characters) before the first deploy. The first start creates the Owner with `ADMIN_EMAIL` and that password, so you can sign in right away. Change the password after you sign in. Later changes to `ADMIN_PASSWORD` have no effect, and you can remove the variable. If the Server does not come up, read the logs of the `llll` container in Coolify; see also [Troubleshooting](https://llll.gg/docs/reference/troubleshooting.md). Coolify generates these values for the template: | Variable | Used as | | --- | --- | | `SERVICE_URL_LLLL_8080`, `SERVICE_FQDN_LLLL_8080` | The Server's public origin (`PUBLIC_ORIGIN`). | | `SERVICE_PASSWORD_64_AUTHSECRET` | `AUTH_SECRET`. Signs sessions and encrypts stored credentials. | | `SERVICE_PASSWORD_64_SETUPTOKEN` | `SETUP_TOKEN`. Entered once on `/setup` to create the Owner. | | `SERVICE_USER_RUSTFS`, `SERVICE_PASSWORD_64_RUSTFS` | Keys for the private storage bucket. | > [!IMPORTANT] > Keep these values private and never regenerate them. A new `AUTH_SECRET` signs out everyone and makes stored credentials unreadable. `APP_NAME` sets the initial name (default `L4`). You can rename the Server later in Settings. ## More addresses To serve more domains, add them in Coolify and list every origin in `LLLL_ORIGINS`, comma-separated. The Server trusts only listed origins. RustFS has no public port. ## Updates The image is built for `linux/amd64` and `linux/arm64`. Only stable releases are published as images, with three tags: the exact version (for example `0.3.5`), its minor line (`0.3`) and `latest`. Updates are controlled in Coolify, not in L4: - **Follow stable releases.** Leave `LLLL_VERSION` unset to use `latest`. Redeploy the service to pull the newest stable release. - **Pin a version.** Set `LLLL_VERSION` to an exact version. Change it and redeploy to update. On start, the new version applies its database migrations and skips ones already applied. Migrations only go forward: an older image refuses to start on data a newer one has migrated. If that happens, set a version at least as new as the one in the error. In L4, **Server settings → Deployment → Updates** shows "Updates are managed outside L4" and tells you when a newer stable release exists. [Screenshot: Server settings → Deployment → Updates on a Docker Server, showing the "Updates are managed outside L4" notice and a newer release link (Docker and Coolify Servers update outside L4)] ## Backups Backups are optional but recommended before an update. - **Server settings → Deployment → Backups** downloads the database and files, or sends them to an S3 destination. - For a full copy, stop the service and archive the `rustfs-data` and `celld-data` volumes. A copy taken while the service runs may be inconsistent. Keep backups outside these volumes. See [Files and backups](https://llll.gg/docs/concepts/files-and-backups.md). ## Optional providers Cloudflare, AI and mail credentials are optional. The template accepts `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`, `AI_BASE_URL`, `AI_API_KEY`, `AI_MODEL`, `MAIL_GATEWAY_URL`, `MAIL_GATEWAY_TOKEN`, `SMTP_GATEWAY_URL`, `SMTP_GATEWAY_TOKEN`, `LLLL_EMAIL_DOMAINS`, `MAIL_APP_HOSTNAME`, `MAIL_DEFAULT_LOCAL_PART`, `INBOUND_EMAIL_SECRET` and `BROWSER_CDP_URL`. See [Optional providers](https://llll.gg/docs/self-host/docker.md#optional-providers) for what each one does. --- # Compute Nodes > Give Agents an isolated place to work on repository Tasks, on your VPS, on Cloudflare Containers, on Lizard or on your own Docker host. Source: https://llll.gg/docs/self-host/compute-nodes A Compute Node is where an Agent's repository work runs. When an Agent works on a repository-backed Task, the Server sends workspace operations (read files, run commands, commit) to a connected Compute Node. The Agent's model and tools stay on the Server; the Node only runs the workspace. Without a connected Node, repository metadata is still available, but Agents cannot write code. This page covers setting up compute. For how Agents use it, see [Repositories](https://llll.gg/docs/agents/repositories.md). ## Choose a kind of compute Open **Server settings → Compute** (requires Manage server). **Add compute** offers **Connect node** and **Connect Lizard**. The **Server compute setup** entry shows the option that fits where your Server runs. | Option | Where it runs | Good for | | --- | --- | --- | | VPS local executor | Docker on the same VPS as a [VPS installer](https://llll.gg/docs/self-host/vps.md) Server | A single trusted VPS. | | Cloudflare Containers | Your Cloudflare account, next to a Cloudflare Server | Cloudflare Servers on Workers Paid. | | Lizard | A Lizard VM created by the Server | Compute without running a machine yourself. | | Remote Docker Node | Any Linux Docker host you pair | Advanced setups. | > [!WARNING] > Docker workspaces share the host's kernel. Use them only for code and people you trust. They are not a boundary against hostile tenants. ## VPS local executor On a VPS installer Server, add the executor during install with `LLLL_FACTORY=true LLLL_SHARED_KERNEL_TRUSTED=1`, or later: ```bash sudo llll compute enable --trust-shared-kernel ``` `llll factory` is the same command. The CLI downloads the verified Compute Node and workspace image from the release, checks the host, installs a root systemd service and pairs it with the Server automatically after the Owner is claimed. No Git clone, source build or registry is needed. The host needs: - Linux amd64 with local Docker. - At least 17 GiB free disk and 2 GiB available RAM beyond what the Server uses. Each new Task workspace gets a 10 GiB disk. - Loop-device and mount support (`fallocate`, `mkfs.ext4`, `mount`, `umount`, `findmnt`, `losetup`). - systemd as process 1. Manage it with: ```bash sudo llll compute status sudo llll compute logs sudo llll compute restart sudo llll compute disable sudo llll compute backup sudo llll compute remove --keep-source ``` `disable` and `remove --keep-source` keep Task disks and receipts. Compute backups are separate from the Server backup made by `llll backup`; copy them encrypted off the host. ### Hosts without systemd In a container or on a host whose process 1 is not systemd, add `--no-systemd`. The CLI prepares the same verified files and writes `factory/factory.env`; you start the Node yourself: ```bash set -a; . /opt/llll/factory/factory.env; set +a /opt/llll/factory/releases/VERSION/node /opt/llll/factory/releases/VERSION/src/pair-local.mjs /opt/llll/.env /opt/llll/factory/releases/VERSION/node /opt/llll/factory/releases/VERSION/src/daemon.mjs ``` In that mode `status` and `logs` fail with an explanation instead. ## Cloudflare Containers A Cloudflare Server can run compute in the same Cloudflare account. Open **Server settings → Compute → Server compute setup** and choose **Enable compute node in Cloudflare**, which opens the official installer with a fresh Cloudflare consent. This needs Workers Paid and available Containers and Artifacts access; the installer never changes your plan. Review the charges, choose compute size, command time and process limits, then return to Settings, confirm the authorization and choose **Activate compute node**. Each Task gets its own code repository. Compute stops after each checkpoint, one operation runs at a time and there is no nested Docker. **Pause compute node and retain source** stops new work and keeps source. See [Costs and limits](https://llll.gg/docs/cloudflare/costs-and-limits.md) and Cloudflare's [Containers pricing](https://developers.cloudflare.com/containers/pricing/). ## Lizard 1. Open **Server settings → Compute → Add compute → Connect Lizard**. 2. Enter your Lizard API key and choose **Check connection**. 3. Choose a project and **Save connection**. Saving starts a temporary compute VM. The VM stops after two minutes without an active Run and starts again on demand. **Stop VM** stops it and prevents automatic restart until you choose **Start compute**. Task workspaces stay on the Lizard disk after the VM stops, so storage charges may continue. Release workspaces before choosing **Delete unused storage**. ## Remote Docker Node You can pair any trusted Linux Docker host with the Server. The Node connects out to the Server; the Server needs no inbound route to it. This works for both Cloudflare and VPS Servers. 1. In **Server settings → Compute → Add compute → Connect node**, name the node and choose **Create pairing code**. The code works once and expires after ten minutes. 2. On the host, build the workspace image from the release you run, push it to your own registry and note its immutable digest. The Node refuses mutable tags. ```bash docker build -f packages/compute-node/Dockerfile.workspace -t YOUR_REGISTRY/l4-workspace:VERSION . docker push YOUR_REGISTRY/l4-workspace:VERSION docker image inspect YOUR_REGISTRY/l4-workspace:VERSION --format '{{json .RepoDigests}}' ``` 3. Start the Node from a checkout of the same release, entering the pairing code without saving it in shell history: ```bash export LLLL_SERVER_ORIGIN=https://YOUR_SERVER export LLLL_NODE_STATE=/PRIVATE/COMPUTE_STATE export LLLL_WORKSPACE_IMAGE=YOUR_REGISTRY/l4-workspace@sha256:DIGEST export LLLL_SHARED_KERNEL_TRUSTED=1 read -r -s -p 'One-use pairing code: ' LLLL_PAIRING_TOKEN export LLLL_PAIRING_TOKEN sudo --preserve-env=LLLL_SERVER_ORIGIN,LLLL_NODE_STATE,LLLL_WORKSPACE_IMAGE,LLLL_SHARED_KERNEL_TRUSTED,LLLL_PAIRING_TOKEN node packages/compute-node/src/daemon.mjs ``` The Node needs Node.js 24, Docker and the loop/mount tools listed above, and runs as root. A remote Server must use HTTPS. Later starts reuse the stored connection and need no pairing code; keep `LLLL_SERVER_ORIGIN` set on every start. No public port is opened. The full threat model and limits are in the [Compute Node guide](https://github.com/inchidev/llll/blob/main/docs/software-factory.md). ## Check a Node Select a Node in **Server settings → Compute** to see whether it is **Connected**, **Offline** or **Disabled**, when it was last seen, its disk, parallel operations and image. **Disable and revoke** stops it from taking new work. [Screenshot: Server settings → Compute with the Server compute setup entry and one connected Docker Node (Compute in Server settings)] To replace a Node, for example after changing its image: export or finish retained work, disable and revoke the old Node, pair a new one, update the repository environment and start new Tasks. --- # Servers and the Shell > How an L4 Server owns its community and data, what the official Shell at llll.gg does, and how Server addresses and identity work. Source: https://llll.gg/docs/concepts/server-and-shell L4 has two parts you deal with as a user: the **Server**, where your community and its data live, and the **Shell**, an optional app that keeps a list of the Servers you use. ## The Server A Server is one community with its own Members, Roles, conversations, content and settings. Each Server is a separate deployment that its operator runs, either on their own Cloudflare account or on their own VPS. There is no hosted L4 service. A Server owns: - Accounts, login and sessions. - Members, Roles and invitations. - Channels, Direct Messages, Tasks, Mail and files. - Its realtime connections and its settings. On Cloudflare, one Server is one Worker with its own D1 database, its own files bucket and its own Durable Objects. On a VPS it is one celld service with its own private storage. Backup storage is always separate from live files. Servers are independent. A Member can belong to several Servers, but access to one Server never implies access to another. Direct Messages stay inside one Server. ### Server identity Every Server has one persistent UUIDv4. It is the Server's only ID. It names the Server's resources, appears in public discovery and is preserved by updates, backups and restores. It is never reused for another Server. Each Server publishes a small public description at `/.well-known/llll`: its UUID, origin, name, version and icon. Names and icons are public branding. Accounts, Members and conversations stay private. ## The Client The Client is the interface a Server serves at its own address. You can always use a Server directly in the browser, without llll.gg. The direct address also works as a PWA. ## The Shell The official Shell at [llll.gg](https://llll.gg) is a web, PWA and Telegram app that shows the Servers you have connected and embeds each Server's own Client. - Saved Servers are **bookmarks on this device**. A bookmark is not an Account and grants no membership. Bookmarks are not synced between devices. - You sign in to each Server separately. Login happens with that Server, in the embedded Client or a popup on the Server's own address. Your password and session never pass through the Shell. - The Shell receives only bounded metadata from the Client: name, icon, unread state, navigation and call events. It never receives message text, passwords, session tokens or provider credentials. - Removing a bookmark does not delete the Server or your Account. The Shell's plus button opens the [Create page](https://llll.gg/create), with three tabs: **Connect** (add a Server by address or invitation), **Cloudflare** (install a new Server on your Cloudflare account) and **VPS** (get the install command for a VPS). **My servers** lists the Servers saved on this device. [Screenshot: The llll.gg Shell with two Servers in the rail and one Server's Client open (The Shell embeds each Server's own Client)] The Shell recognizes a Server by its UUID. If a bookmarked address starts answering with a different UUID, you must reconnect explicitly. ## Addresses A Server can answer at several public **addresses**: - The **primary address** is used for new links, Shell display and background email. - **Aliases** are additional addresses that reach the same Server UUID, Accounts and content. Every listed address is trusted for login, sessions, embedding, uploads and realtime. A hostname that is not listed is not a Server address, even if it reaches the Server. On Cloudflare, addresses are managed in **Server settings → Deployment → Domain**; on a VPS, with `llll domain` ([VPS reference](https://llll.gg/docs/self-host/vps.md#server-addresses)). ## Related - [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md) - [Connect a Server](https://llll.gg/docs/get-started/connect-a-server.md) - [Custom domain on Cloudflare](https://llll.gg/docs/cloudflare/custom-domain.md) --- # Accounts, Members and Roles > How login identity, membership and permissions work on an L4 Server, including the Owner, built-in Roles, Project policies and Grants. Source: https://llll.gg/docs/concepts/accounts-members-roles Every Server keeps its own Accounts, Members and Roles. Nothing here is shared between Servers. ## Account An Account is a human's login identity on one Server. Accounts on different Servers are independent: the same person has a separate Account, and a separate password, on each Server. - **Login methods.** The Owner chooses them in **Server settings → Authentication**: email and password, and social providers the Owner configures. A Server can instead use Cloudflare Access, which then owns login. - **Linking.** A login method joins an Account only through explicit linking. A matching email address never links Accounts and never grants access. - **Recovery.** In your own settings, **Account recovery** creates a recovery key for this Server. It is shown once. Using it sets a new password, uses up the key and signs out your other sessions. Replacing the key invalidates the previous one. ## Member A Member is a human or an Agent taking part in one Server. A human's Account says who they are; their membership on this Server decides what they can access. An Account and a Member are different things: removing someone's membership does not delete their identity on other Servers, and an Agent is a Member without an Account. People join through invitations created by Members with **Create invites**. A new person may need to accept the Server's terms and rules before they become an active Member. ## Owner The Owner operates the Server. The Owner is created once, when the Server is first claimed through its private setup link, and holds every permission. Only the Owner manages deployment settings such as updates, backups, addresses, storage and authentication. Owner rights cover administration, not private content. Being the Owner does not add you to private channels, and does not let you read other people's private Agent Runs. ## Role Each Member has one Server Role. A Role groups permissions. Every Server has three system Roles: | Role | Default permissions | | --- | --- | | Owner | All permissions. Cannot be assigned to an Agent. | | Admin | Built-in policy "Server administrator": every permission except Execute assigned tasks, Use a computer, Manage schedules, Propose code changes and Connect MCP clients. | | Member | Built-in policy "Server member": Send messages, Attach files and Start live sessions. Chat only. | You can create more Roles in **Server settings → Roles & Policies**. A Role is a collection of reusable policies. New Roles start without policies. Only the Owner can add policies to Admin and Member (for example, Use agents for everyone); their built-in policy always stays. Role administrators cannot grant permissions they do not have themselves. A Role can also limit which Boards, Tables, Documents and Canvases its holders can reach. Selecting no resources of a kind permits none of them. [Screenshot: Server settings → Roles & Policies with the system Roles Owner, Admin and Member and one custom Role (Roles & Policies)] ### Some permissions to know | Permission | Allows | | --- | --- | | Manage server | Change the Server name and settings. | | Manage roles | Create Roles, change grants and assign Roles. | | Manage channels | Create, edit and remove channels; use `@channel` and `@here`. | | Create invites | Invite new Members. | | Read tasks, Create and update tasks, Manage tasks | Read, write and administer Task boards. | | Use agents | Find Agents, talk to them and give them instructions. | | Manage agents | Create Agents and change their profiles and models. | | Approve agent actions | Review and approve an Agent's pending actions. | | Read email, Send email | Use Mailboxes shared with the Member. | Agent autonomy permissions (Execute assigned tasks, Use a computer, Manage schedules, Propose code changes) are always explicit and never inferred from an administrator Role. ## Project policy and Grant A **Project policy** adds permissions for a person, an Agent or a Server Role inside one Project. The same Member can have Developer access in Project A and Reviewer access in Project B. Policies only add. They never remove broader rights a Server Role already gives. A **Grant** permits one operation on a resource. The Server checks each operation in its Project and resource scope, every time. Project access does not grant Server administration or membership of private channels. ## Where permissions are enforced The interface hides what you cannot do, but the Server itself is the boundary. The same checks apply to the web Client, MCP connections and Agents. When a Member's Role changes or they are removed, their access changes immediately, including open realtime connections. ## Related - [Invite your team](https://llll.gg/docs/get-started/invite-your-team.md) - [Agents, Tasks and Runs](https://llll.gg/docs/concepts/agents-tasks-runs.md) - [Glossary](https://llll.gg/docs/reference/glossary.md) --- # Agents, Tasks and Runs > How Agents work as governed Members of a Server, how Tasks and Runs relate, and what limits every Run. Source: https://llll.gg/docs/concepts/agents-tasks-runs Agents in L4 are Members of a Server, not outside bots. They work under the same permission model as people, and every piece of work they do is recorded as a Run. ## Agent An Agent is a non-human Member with one assigned Role. Its profile holds its name, instructions and model. An Agent is never a login Account. - The Agent's Role caps what it can do. The Owner Role can never be assigned to an Agent. - People need **Use agents** to find an Agent and talk to it. Without it, the Agent is hidden from them. - An Agent takes part only in conversations it can access. Only a direct mention addresses an Agent; role mentions and `@channel` never do. - Sending external email always needs a human's approval. An **external Agent** works on its own computer instead of with this Server's models. A mention or Direct Message wakes it through its webhook, and it answers and completes the Run through the Server's [MCP endpoint](https://llll.gg/docs/agents/mcp.md), as itself and within its Role. See [External agents](https://llll.gg/docs/agents/external-agents.md). ## Task A Task records a piece of work and who is responsible for it. Tasks live on Boards, can belong to a Project and can be created from a chat message (**Create task**), linked both ways. Assigning a Task to an Agent does not start it. Someone with the right permissions chooses **Run assigned agent**. Task execution needs Read tasks, Execute assigned tasks and Send messages, for both the Agent and the person who starts it. ## Session, Run and Environment - A **Session** holds one Agent's context for one conversation. - A **Run** is one attempt to carry out work. Every reply to a message is a Run, and a Run may be linked to a Task. - An **Environment** supplies the tools and working files a Run can use, for example a repository workspace on a [Compute Node](https://llll.gg/docs/self-host/compute-nodes.md). **Runs** lists the Runs you started. Each Run page shows its activity, model requests and result. Run content is private to the person who started it; the Owner sees usage metadata (who, which Agent, status, model, tokens), not the prompts or output. ## Two sets of permissions Every Run is checked against both the Agent's Role and the permissions of the person who started it. The Agent can never do more than either of them allows. Invoking an Agent as the Owner does not make the Agent an Owner. These checks run on every model and tool call, not once at the start. If the Agent is paused or removed, or either side loses access, the Run stops being able to act. Some abilities are always explicit opt-ins on a Role, never implied by an administrator Role: | Permission | Lets an Agent | | --- | --- | | Execute assigned tasks | Work on Tasks assigned to it. | | Use a computer | Use isolated workspace tools on a Compute Node. | | Propose code changes | Push to a working branch and open draft pull requests. It cannot merge or release. | | Manage schedules | Create reminders and recurring work. | | Research the web | Search and read public web pages through configured providers. | ## Limits Runs are bounded. The Owner can set limits for new Runs in **Server settings → Agents → Execution**. The defaults are six hours, 256 steps and model calls, 4,096 tool calls and two million tokens per Run, and 500 Runs and 20 million tokens per Agent in 24 hours. A tool call whose outcome is uncertain is never silently repeated. ## Schedules A Schedule starts a reminder or work at a set time, once, daily or weekly. It keeps the permission ceiling it was saved with and checks current membership and grants before each delivery. If permissions change, a Schedule can become blocked until you edit it. See [Schedules](https://llll.gg/docs/agents/schedules.md). ## Related - [Agents overview](https://llll.gg/docs/agents/overview.md) - [Models and providers](https://llll.gg/docs/agents/models-and-providers.md) - [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md) --- # Files and backups > Where an L4 Server stores messages and files, what a Backup contains and leaves out, and why backups live apart from live storage. Source: https://llll.gg/docs/concepts/files-and-backups A Server keeps its data in its own storage. A Backup is a recovery copy kept somewhere else. It is not a second running Server. ## Where data lives | Data | On Cloudflare | On a VPS (celld) | | --- | --- | --- | | Messages, Accounts, Roles, Tasks, Mail records, settings | The Server's D1 database | celld's D1, stored in the private RustFS bucket | | Files and attachments, raw email | The Server's private R2 files bucket, or Basic KV storage | The same RustFS bucket | | Live state (presence, typing, active calls) | Durable Objects | celld Durable Objects | | Backups | A separate backups bucket, or a download | Archives outside the live volumes | Every file read goes through the Server's normal authorization. Files are never served from a public bucket. ### Basic KV storage on Cloudflare A Cloudflare Server can run without R2. File payloads are then stored in the Server's existing KV namespace, with a limit of 20 MiB per file and 500 MiB in total. Cloudflare's own KV limits and daily quotas still apply. To move to R2, open **Server settings → Storage**, choose **Connect R2**, then **Move files to R2**. The copy runs in resumable batches and checks every file's hash. Uploads pause while it runs; reading continues. The original KV copies are kept. If a Server has no file storage at all, uploads and full backups fail with "File storage is unavailable. Ask the Server operator to configure storage." ## What a Backup contains The Server's own backup (**Server settings → Deployment → Backups**) is a TAR archive with: - A logical export of the D1 database: messages, authentication records, Roles and all other Server data. - The live files (R2 objects, or KV file payloads on Basic storage). - A summary of table, row, object and byte counts. It does **not** contain: - Environment secrets, including `AUTH_SECRET`. - Live Durable Object state. - KV connection tickets. - Data held by external providers, such as RealtimeKit. > [!IMPORTANT] > Keep the original `AUTH_SECRET` (and any provider secrets) safe and separate from the archive. Stored credentials, such as model provider keys, backup keys and the Server's push notification identity, are encrypted with it. A restore with a different secret cannot decrypt them. ## Where Backups go On Cloudflare, the installer creates a separate backups bucket next to the files bucket. If it exists, the Server writes a backup there every week, and before every update. Without one, Settings downloads the archive to your computer. You can also save S3 credentials for a separate destination of your own; use a private bucket that is not the files bucket L4 already uses. On a VPS, `llll backup` archives the whole installation, including `.env` and all volumes. On Docker Compose or Coolify you stop the stack and archive the volumes yourself. See [VPS](https://llll.gg/docs/self-host/vps.md#backups) and [Docker Compose](https://llll.gg/docs/self-host/docker.md#back-up-and-restore). [Screenshot: Server settings → Deployment → Backups with the Download backup button and the archive scope note (Backups in Deployment settings)] ## Backups stay separate - Backups are never stored in the live files bucket. Otherwise later archives could include earlier ones, and one bucket failure could remove both the files and their backups. - Removing a Server never deletes its backup bucket or backup archives. - Restoring a backup brings back the same Server UUID, Accounts, Roles, messages and files. Updates make a verified backup first. See [Releases and updates](https://llll.gg/docs/concepts/releases-and-updates.md). ## Related - [Backups on Cloudflare](https://llll.gg/docs/cloudflare/backups.md) - [Remove a Server](https://llll.gg/docs/cloudflare/remove-a-server.md) --- # Releases and updates > How L4 versions and releases work, and how each kind of Server (Cloudflare, VPS, Docker, Coolify) receives an update. Source: https://llll.gg/docs/concepts/releases-and-updates L4 ships as numbered releases on [GitHub](https://github.com/inchidev/llll/releases). Your Server changes only when its operator chooses an update. Nothing updates it automatically. ## One shared version One version number covers every part of L4: the Landing page, Shell and installer at llll.gg, the Server, the portable runtime image and the Docs. Release notes for each version are published with the release. - **Stable releases** have a plain version, such as `0.3.4`. - **Prereleases** have an alpha, beta or rc suffix, such as `0.2.0-alpha.7`. You must choose them explicitly. ## Releases are immutable A published release tag and its files are never replaced. Every release includes a `SHA256SUMS` file. Installers and updaters check the checksums before they install anything, and they accept only published releases: never unreleased or locally built code. If a release turns out to be wrong, the fix comes in a new version. ## Database lineage Since 0.3.0, Servers use the `server-v2` database lineage. A 0.3.x Server needs a fresh database: 0.2.x Servers cannot be upgraded into it and stay on their selected release. Within the lineage, migrations are only added, never rewritten, and they apply automatically during an update. Migrations only go forward. There is no automatic downgrade. To go back, restore a backup. ## How updates arrive | Server | How to update | | --- | --- | | Cloudflare | **Server settings → Deployment → Updates** in the Server. | | VPS installer | `sudo llll upgrade` on the VPS. | | Docker Compose | Set a newer image in `LLLL_IMAGE` and restart the stack. | | Coolify | Redeploy with `latest`, or change `LLLL_VERSION`. | ### Cloudflare The Owner opens **Server settings → Deployment → Updates**, chooses a newer release and selects **Back up and update**. **Include prereleases** adds alpha, beta and rc releases to the list. Only releases newer than the running version can be chosen. No Git, GitHub account or Workers Builds is needed. There are two ways the update runs: - **On the Server.** If the Server keeps Cloudflare access (**Keep Cloudflare access on this Server**, on by default when installing), it updates itself. It verifies the release, writes a backup to the separate backups bucket and reads it back, uploads the release and lets the new version confirm itself. You can close the page. **Retry update** resumes after the verified backup. - **Through the official installer.** Without kept access, Settings sends you to the installer at llll.gg, which asks for fresh Cloudflare consent, makes and verifies the backup, then uploads the release. The update keeps the Server's bindings, secrets, UUID and Durable Objects. See [Update a Cloudflare Server](https://llll.gg/docs/cloudflare/updates.md). [Screenshot: Server settings → Deployment → Updates on a Cloudflare Server with a newer release selected and the Back up and update button (Updating a Cloudflare Server)] ### VPS installer `sudo llll upgrade` installs the latest stable release. It takes a mandatory backup first and restores the previous version and data if the new one fails to start. See [VPS](https://llll.gg/docs/self-host/vps.md#upgrade). ### Docker Compose and Coolify Stable releases are published as `ghcr.io/inchidev/llll:`, `:` and `:latest`, for `linux/amd64` and `linux/arm64`. Prereleases are not published as images. On start, the container applies new migrations and refuses an image older than the release that last migrated its data. In these Servers, **Server settings → Deployment → Updates** says that updates are managed outside L4 and shows when a newer stable release exists. See [Docker Compose](https://llll.gg/docs/self-host/docker.md#update) and [Coolify](https://llll.gg/docs/self-host/coolify.md#updates). ## After an update When the new Client is ready, Settings offers to load the new interface. Check that Owner and Member login, permissions, files and realtime work, and that the Server UUID is unchanged. --- # Agents overview > How Agents work in an L4 Server, what they can do with only a model, and what code work, schedules and external tools add. Source: https://llll.gg/docs/agents/overview An Agent is a non-human Member of your Server. It has a name, profile instructions, a model and one Role. People talk to it in chat or give it Tasks, and every reply or piece of work is a **Run**. To create your first one, follow [Create your first Agent](https://llll.gg/docs/get-started/first-agent.md). This section covers the parts you set up as an operator or Agent builder: | Page | Use it to | | --- | --- | | [Models and Providers](https://llll.gg/docs/agents/models-and-providers.md) | Connect OpenRouter, Claude, ChatGPT or Workers AI and choose defaults and AI functions. | | [MCP](https://llll.gg/docs/agents/mcp.md) | Connect an MCP client such as Codex or Claude to your Server, as yourself or as an Agent. | | [External agents](https://llll.gg/docs/agents/external-agents.md) | Add an Agent that runs on its own computer and answers through MCP. | | [Repositories](https://llll.gg/docs/agents/repositories.md) | Let Agents take a Task to a draft pull request on GitHub. | | [Schedules](https://llll.gg/docs/agents/schedules.md) | Set reminders and recurring Agent work. | ## Where Agent settings live Open the Server menu (the Server's icon at the top of the rail) and choose **Server settings**. The **Agents & code** group holds: - **Models**: model Providers, the Server default model and AI functions. - **Agents**: create, edit, pause and delete Agents. The Owner also sees **Execution** limits and an **Audit** tab. - **Git**: GitHub connections and repository environments. - **Compute**: Compute Nodes where code work runs. - **Tools**: read-only external MCP sources for Agents. Which items you see depends on your Role. **Agents** needs **Manage agents**; **Git** and **Compute** need **Manage server**. [Screenshot: Server settings with the Agents & code group showing Models, Agents, Git, Compute and Tools (Agent settings in Server settings)] ## Talking to an Agent - **Direct Message:** open the **Agents** rail section and select an Agent. Each message you send starts a reply Thread; replies in that Thread keep using it. - **Mention:** mention the Agent directly in a channel it can access. Only a direct mention addresses an Agent; role mentions and `@channel` or `@here` never wake one. Agent replies go into a Thread. People need **Use agents** in their Role, or in a Project policy, to find and talk to Agents. Without it, Agents are hidden from them. In a one-to-one Agent conversation, the toolbar above the composer lets you: - choose the **Provider / model** for future Runs (Threads inherit the conversation's choice); - choose a **Reasoning effort**: Auto, Low, Medium or High; - switch between **Queue** and **Steer**. Queue runs your messages one after another. Steer adds a new instruction to your running Run at its next tool step; if steering is not available, the message stays queued; - stop the current work with **Stop Agent**. A Run that has started keeps the model it started with. Changing the picker affects only later Runs. ## What an Agent can do With only a model, an Agent can chat in conversations it can access and use the Server's built-in tools that its Role and your permissions allow, for example reading and updating Tasks. It finds tools by searching for them during the Run; it cannot use a tool neither of you is allowed to. More capabilities need explicit setup and permissions: | Capability | Needs | | --- | --- | | Web search and reading pages | **Research the web** for both the Agent and the person asking, plus a search provider (see [Models and Providers](https://llll.gg/docs/agents/models-and-providers.md#ai-functions)). | | Working on a Task | **Read tasks**, **Execute assigned tasks** and **Send messages** for both the Agent and the person who starts it. | | Code work and draft pull requests | **Use a computer** for both, **Propose code changes** for the Agent, a Git connection, a repository environment and compute. See [Repositories](https://llll.gg/docs/agents/repositories.md). | | Reminders and recurring work | **Manage schedules**. See [Schedules](https://llll.gg/docs/agents/schedules.md). | | External read-only MCP tools | **Use external tools** for both. See [MCP](https://llll.gg/docs/agents/mcp.md#external-tools-for-agents). | | Images and voice messages | A model that accepts images, or the **Image descriptions** and **Voice transcription** AI functions. | Sending external email always needs a human. In this release Agents cannot send external email at all; only the Mail interface sends. ## Permissions: the Agent and you Every Run is checked against the Agent's Role and against the person who started it, on every model and tool call. The Agent can never do more than either of you. Asking an Agent as the Owner does not make the Agent an Owner. If the Agent is paused or removed, or either of you loses access, the Run can no longer act. To give project-specific access, assign a Project policy in **Projects → Access**. The same Agent can be a Developer in one Project and a Reviewer in another. **Fill instructions from an example** in the Agent editor only fills in text; it grants nothing. See [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md). ## Privacy of Runs Run content is private to the person who started it: its prompt, events, transcript and working files. The **Runs** page lists only your own Runs. After a Run reads a private source, such as Tasks, private repositories or mail, its full answer is posted only in your own Direct Message with the Agent. Other conversations receive a link to the private Run. In that same conversation, the Agent can then no longer reach arbitrary web or MCP destinations, so private data cannot be carried out. Start a new conversation for unrelated web research. On a completed private Run, someone with **Approve agent actions** who started it can choose **Review sharing**, check the exact text and its audience, and select **Approve and publish** to post it. The Owner can see usage metadata in **Server settings → Agents → Audit**: who started a Run, which Agent, status, model, call counts and tokens. The Owner cannot read another person's prompts or output. ## Execution limits The Owner sets limits for new Runs in **Server settings → Agents → Execution**: - **Execution mode**: **Autonomous within granted permissions** (the default) or **Review writes and outbound calls**, which pauses workspace operations for approval and blocks other writes and outbound calls. - Limits per Run, such as steps, model calls, tool calls, tokens and duration. The defaults are six hours, 256 steps and model calls, 4,096 tool calls and two million tokens. - Limits per Agent over 24 hours. The defaults are 500 Runs and 20 million tokens. Existing Runs keep the limits they started with. Merge and release tools are never available to Agents. ## Pause or delete an Agent Open the Agent in **Server settings → Agents**. - Set **Status** to **Paused** to stop new work. Everything is kept. - **Delete agent** removes it from the Server and frees its name. Its queued and running Runs are cancelled, its Schedules stop, its MCP connections are revoked and its open Tasks become unassigned. Its messages, Direct Messages, Run history and audit entries stay. This cannot be undone. ## Related - [Agents, Tasks and Runs](https://llll.gg/docs/concepts/agents-tasks-runs.md) - [Projects and Tasks](https://llll.gg/docs/guides/projects-and-tasks.md) --- # Models and Providers > Connect OpenRouter, Claude, ChatGPT or Workers AI to your L4 Server, choose a default model and assign models to AI functions. Source: https://llll.gg/docs/agents/models-and-providers Agents need a model to reply. You connect model **Providers** in **Server settings → Models**. API keys are stored on your Server, encrypted with its `AUTH_SECRET`, and are never shown to Members or sent to the browser. The Owner manages Server Providers and defaults. Members with **Use agents** see the same page but can only connect their own ChatGPT plan; they see which models are available, never the credentials. ## Supported Providers | Provider | How it connects | Who can add it | | --- | --- | --- | | OpenRouter | API key. Hundreds of models with one key. | Owner | | Claude | Anthropic API key. | Owner | | ChatGPT / Codex | **Continue with ChatGPT** sign-in with your ChatGPT plan. | Owner, or any Member with **Use agents** for their own plan | | Workers AI | Comes with a Cloudflare Server's deployment, billed to its Cloudflare account. | Listed automatically; the Owner can turn it off | Claude Pro or Max subscriptions cannot be used: the Claude Provider needs an Anthropic API key. ## Add an API key Provider 1. Open **Server settings → Models**. The Owner sees the **Providers** and **Defaults** tabs. 2. Select **Add provider** and choose **OpenRouter** or **Claude**. 3. Optionally change the **Connection name**, for example to tell a work account from a personal one. You can add several connections of the same Provider. 4. Paste the **API key**. 5. In **Models**, use **Add a model…** to pick models from the Provider's catalog. Each model shows what it accepts (text, images, audio, video, files) and whether it reasons. 6. Keep **Available to agents** on and select **Save**. [Screenshot: The Add provider form in Server settings → Models with the OpenRouter, Claude and ChatGPT / Codex cards, the API key field and the Models picker (Add a model Provider)] To change a key later, open the connection and paste a new one. Leaving **API key** empty keeps the saved key. **Remove connection** deletes it. Models listed for a connection appear in Agent and conversation model pickers. Models without tool support are offered to AI functions only. > [!TIP] > Models chosen before L4 recorded model capabilities may show none. Open the Provider and select **Save** once to refresh them. ### OpenRouter web search An OpenRouter connection has a **Web search** switch. It lets Agents with **Research the web** search through OpenRouter. Additional OpenRouter charges apply. To use it, also assign a model to the **Web search** AI function (see below). ## Connect ChatGPT ChatGPT uses your ChatGPT plan instead of an API key. 1. In **Server settings → Models**, select **Add provider** (Owner) or **Connect ChatGPT** (Members), and choose **ChatGPT / Codex**. 2. Select **Continue with ChatGPT** and approve in the ChatGPT tab that opens. 3. ChatGPT then opens a page at an address starting with `http://127.0.0.1:1455` that cannot load. That is expected: OpenAI returns the sign-in only to your own computer. Copy that page's full address. 4. Paste it into L4 and select **Finish connecting**. The address works once and expires in 10 minutes. When L4 runs on your own computer at `localhost` or `127.0.0.1`, the sign-in finishes on its own and you do not need to paste anything. By default, a ChatGPT connection serves only the person who connected it. To let others use it, the person who connected it turns on **Share with everyone on this Server**. Every Member's Agents and AI functions can then use that plan, and their requests count against its limits. Only the person who connected it can change this. Connecting ChatGPT does not sign anyone in to L4 or grant membership. Removing the connection stops its use in L4 and tries to revoke the sign-in. If L4 says remote revocation was not confirmed, disconnect L4 in your ChatGPT settings. ## Workers AI On a Cloudflare Server, Workers AI appears at the top of the Providers list as **Included with this Server**. It cannot be removed here. Open it to turn **Available to agents** off or choose which models appear in model pickers. ## Choose a default model Connecting a Provider never selects a default. Without a default, each Agent needs its own model. 1. Open **Server settings → Models → Defaults**. 2. Under **Model defaults**, choose a **Default model**, or **No default model**. An Agent's own model overrides the default, and a conversation's model picker overrides both. If a chosen model is disabled or unavailable, Runs fail with an explanation; L4 never silently switches to another model. ## AI functions AI functions are small Server jobs that use a model directly, without an Agent. Assign them in **Server settings → Models → Defaults → AI functions**. All start **Disabled**, and none inherits the default model. | Function | What it does | | --- | --- | | Help with Apps | Drafts Apps from Database Views. Apps are paused in this release. | | Thread titles | Names new Threads from their first message. Existing titles stay. | | Web search | Search for Agents. Choose an OpenRouter model with web search enabled. An Exa key configured by the operator takes precedence. | | Avatars | Draws profile pictures for Members and Agents. Needs an OpenRouter model that outputs images, such as Nano Banana. | | Image descriptions | Describes attached images for Agents whose model cannot see them. | | Voice transcription | Turns voice messages into text for Agents. | Each function lists only models that can do the job. Descriptions and transcripts reach the Agent marked as untrusted data. AI function calls are limited to 20 per Member per minute. Personal ChatGPT connections that are not shared serve only their owner's requests; other Members see them as unavailable. ### Generate avatars With **Avatars** assigned, profile and Agent settings show **Generate**. Describe the picture, optionally add up to three reference images, and keep **L4 style** for a toy-like black-and-white character shaped after the L4 logo. Preview it, then select **Use this picture**. ## Images and voice When a message has images attached, the Agent receives the sender's own images from that conversation if its model accepts images. If it does not, the **Image descriptions** function supplies a description instead; without either, the Agent says it cannot view them. Voice messages reach Agents as text through **Voice transcription**. ## Related - [Agents overview](https://llll.gg/docs/agents/overview.md) - [Create your first Agent](https://llll.gg/docs/get-started/first-agent.md) - [Costs and limits on Cloudflare](https://llll.gg/docs/cloudflare/costs-and-limits.md) --- # Connect an MCP client > Connect Codex, Claude or another remote MCP client to your L4 Server with OAuth or an access token, and use its chat, Task and Mail tools. Source: https://llll.gg/docs/agents/mcp Every L4 Server is an MCP server. An MCP client, such as Codex, Claude or your own agent, can read and send messages, work with Tasks and triage shared Mailboxes on your Server, as you or as an Agent you manage. It always acts within that Member's current Role. There is no central L4 account involved: you connect to one Server's address and sign in with your Account on that Server. ## The MCP endpoint Each Server serves Streamable HTTP MCP at: ```text https:///mcp ``` You can copy the exact URL from **User Settings → Connected apps** (**MCP Server URL**). Open User Settings from your avatar at the bottom of the rail. ## Before you start The acting Member, and the person approving the connection, need **Connect MCP clients** in their Role. It is off by default for every Role except Owner, who always has it. Someone with **Manage roles** adds it through **Server settings → Roles & Policies**: put it in a policy (it is under **Tools & integrations**) and add that policy to the Role. This permission only allows the connection. It adds no Task, message or mail permissions. The client must be able to reach your Server's address. If you put an access gate in front of the Server, it must let the OAuth discovery and token requests and `/mcp` through without an interactive login; the MCP token is the authorization. ## Connect with OAuth Use OAuth when your client supports remote MCP servers with OAuth sign-in. This is the recommended way. 1. In your MCP client, add a remote (HTTP) MCP server with your Server's `/mcp` URL. If the client asks how to register, choose automatic (dynamic) registration. 2. The client opens your Server in a browser. Sign in with your Account on that Server if needed. 3. On **Connect an MCP client**, check the client name and its callback. The client name is supplied by the application itself and does not prove who made it. 4. Under **Act as**, choose yourself, or an Agent you manage. Only the Owner and Members with **Manage server** can connect as an Agent. Nobody can connect as another person. 5. Under **Allow this application to**, choose the scopes to grant. 6. Select **Allow access**, or **Deny**. [Screenshot: The Connect an MCP client consent page with the client name, the Act as select, the scope checkboxes and the Allow access button (Approve an MCP client)] Access tokens last 15 minutes and refresh automatically for up to 30 days while the connection stays valid. ### Scopes | Scope | Allows | | --- | --- | | `tasks:read` | Read tasks | | `tasks:write` | Create and update tasks | | `messages:read` | Read accessible chats, messages and members | | `messages:write` | Send messages and mention members in accessible chats | | `mail:read` | Read and search email in Mailboxes shared with you | | `mail:write` | Mark email read and move conversations between folders (does not send email) | Mail scopes are offered only when the acting Member's Role has **Read email** or **Send email**. Scopes can only narrow what the Member's Role allows; they never add to it. Data and Document scopes exist but are paused in this release, like Data itself. A connection keeps the scopes it was approved with. To add a scope later, connect again and approve it. ## Connect with an access token Some clients and scripts cannot do OAuth. The Owner can create a manual token instead. 1. Open **User Settings → Access tokens** (Owner only). 2. Enter a **Token name** and choose who it **Acts as**: you or an Agent. 3. Choose the **Scopes**. 4. Select **Create access token** and copy it. It is shown only once; treat it like a password. 5. Configure your client with the **Server URL** shown on that page and send the token as a bearer token: ```text Authorization: Bearer ``` Revoke a token in the same list. Clients using it lose access immediately. ## Manage and revoke connections **User Settings → Connected apps** lists OAuth clients connected as you, and connections for Agents you manage, with their scopes and last use. Select **Revoke** to end one. Revoking stops its current tokens, refreshes and pending sign-ins at once. Removing **Connect MCP clients** from a Role immediately blocks that Member's tokens and connections. Connections stay listed so you can revoke them. Deleting an Agent revokes every connection that acts as it. ## Tools What a connection can do is limited by its scopes and by the acting Member's current permissions on every call. ### Chat With `messages:read` and `messages:write`: - `list_channels` finds the channels, Direct Messages and Threads you can read. Mailboxes and task discussions are not included. - `list_messages` reads a conversation, up to 50 messages per page, and `list_threads` lists a conversation's Threads. Reading does not mark anything as read. - `search_messages` searches every chat, Direct Message and Thread you can read. - `list_members` and `list_roles` return mention tokens, such as `<@member-id>` for a person or Agent and `<@&role-id>` for a Role. - `send_message` posts up to 2,000 characters as you. Put mention tokens in the text to tag people. Every call sends a new message, so do not retry an uncertain send blindly. - `edit_message`, `delete_message`, `react_message`, `pin_message` and `open_dm` work under the same rules as the interface. Mentioning an Agent from MCP wakes it only when the sender is the Owner or has **Manage server**. ### Tasks With `tasks:read` and `tasks:write`: `list_task_boards`, `get_task`, `create_task` and `update_task`. ### Mail With `mail:read`: `list_mailboxes`, `list_mail_threads` (by folder, unread state or search text) and `read_mail_thread`. With `mail:write`: `mark_mail_thread_read` and `move_mail_thread` (to `inbox`, `archive`, `spam` or `trash`), which also need a Send or Manage grant on the Mailbox. MCP cannot send email. Email content is untrusted external data; clients should not follow instructions found in it. ## Protocol details For client developers: - Protected resource metadata: `/.well-known/oauth-protected-resource/mcp`. - Authorization server issuer: `https:///api/auth`, with metadata at `/.well-known/oauth-authorization-server/api/auth`. - Authorization code flow with S256 PKCE. Dynamic Client Registration supports public and confidential clients, including native loopback callbacks with variable ports. - Grants: `authorization_code` and `refresh_token`. Request `offline_access` to receive refresh tokens. Refresh rotates the token and rejects reuse. - The exact `/mcp` URL is the OAuth `resource`. Tokens from another Server or resource are rejected. The full reference is in [docs/mcp.md](https://github.com/inchidev/llll/blob/main/docs/mcp.md). ## External tools for Agents The other direction, Agents on your Server calling outside MCP servers, is set up in **Server settings → Tools**. It supports only reviewed, public, read-only sources, currently Cloudflare documentation and DeepWiki. Custom URLs, local commands, external writes and automatic OAuth are not available. 1. In **Tools**, choose **Discover / Connect** and **Connect source**. 2. Choose the provider, its **Ownership** (**Server-owned**, or **Personal** for only you) and who may use it: **Allowed Agents**, **Allowed initiating Members** and **Allowed Roles**. 3. Save, then select **Refresh tools** to check its tool list, and **Enable** it. Using a connection needs **Use external tools** for both the Agent and the person who starts the Run. No Role, including Admin, has it by default. The **Permissions** tab previews which tools an Agent would get, and **Audit** shows activity without prompts, arguments or results. ## Related - [External agents](https://llll.gg/docs/agents/external-agents.md) - [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md) --- # External agents > Add an L4 Agent that runs on its own computer. A mention wakes its webhook, and it answers and completes the Run through the Server MCP endpoint. Source: https://llll.gg/docs/agents/external-agents An external Agent works on its own computer, such as a bot you run elsewhere, and never uses your Server's model Providers. It is still a normal Agent Member of your Server: it has a Role, appears in the member list and can be mentioned. When someone mentions it, the Server calls its **webhook**; the Agent then answers through the Server's [MCP endpoint](https://llll.gg/docs/agents/mcp.md) as itself, with exactly its Role's permissions. ## How a Run works 1. Someone mentions the Agent directly, or messages it in a Direct Message. This starts a Run under the usual rules: the same Role, permissions, conversation queue and daily Run limit as any Agent. Role mentions and `@channel` or `@here` never wake it. 2. The Server sends one `POST` to the Agent's webhook with the Run, the message and where to reply. 3. The Agent connects to the Server's `/mcp` endpoint and posts its answer with `send_message` in the Run's conversation. It can post as many messages as it likes; messages never close the Run. 4. The Agent calls `complete_run` when it is done. 5. If it never calls `complete_run`, the Run settles at the **Reply timeout**: completed with its latest message if it replied, failed otherwise. ## Set up an external Agent You need **Manage agents** to create the Agent, and the Owner to create its access token. ### 1. Create the Agent 1. Open **Server settings → Agents** and select **Create agent**, or edit an existing Agent. 2. Enter a **Name**. 3. Under **Works on**, turn on **Its own computer (external agent)**. 4. Enter the **Webhook URL**. It must be a public `https://` address. 5. In **Key header**, enter the header your webhook expects the key in. It defaults to `Authorization`. 6. In **Sender key**, enter the value to send in that header exactly as the receiver expects it, for example `Bearer …`. 7. Set the **Reply timeout**: 2 to 120 minutes, 20 by default. 8. Choose its **Role** and select **Create agent**. [Screenshot: The Create agent dialog with Its own computer (external agent) turned on, showing the Webhook URL, Key header, Sender key and Reply timeout fields (Create an external Agent)] The sender key is stored encrypted and never shown again. Leave **Sender key** empty to keep the saved key; if you change the URL or header, enter the key again. ### 2. Give its Role what it needs The Agent's Role needs at least: - **Connect MCP clients**, so its token works; - **Send messages** in the conversations where it should answer. Add whatever else it should be able to do, for example reading Tasks. People who should be able to talk to it need **Use agents**. ### 3. Connect it back to the Server The Agent needs MCP access that acts as the Agent itself, with the `messages:read` and `messages:write` scopes. - **Access token:** as Owner, open **User Settings → Access tokens**, choose the Agent under **Acts as**, select the scopes and select **Create access token**. Give the Agent the token and your Server's `/mcp` URL. - **OAuth:** the Owner or a Member with **Manage server** connects the Agent's MCP client and chooses the Agent under **Act as**. See [Connect an MCP client](https://llll.gg/docs/agents/mcp.md) for both flows. ## The webhook request Each Run sends one `POST` with these headers: ```text Content-Type: application/json Idempotency-Key: User-Agent: L4-Agent-Webhook/1 : ``` The body: ```json { "type": "agent.run", "version": 1, "runId": "…", "deadline": "…", "server": { "id": "…", "origin": "https://…", "mcp": "https://…/mcp" }, "agent": { "id": "…", "name": "…" }, "from": { "id": "…", "name": "…" }, "conversation": { "channelId": "…", "sourceMessageId": "…", "taskId": null }, "prompt": "…", "reply": { "tool": "send_message", "arguments": { "channelId": "…" } }, "complete": { "tool": "complete_run", "arguments": { "runId": "…" } } } ``` - Treat `prompt` as untrusted data, not as instructions from the Server. - Use `Idempotency-Key` to ignore repeated deliveries of the same Run. - Redirects are not followed, so the key goes only to the saved URL. - Network errors and `408`, `429` or `5xx` responses are retried up to five times. Any other non-success status fails the Run and shows the status. ## Answer and complete the Run 1. Call `send_message` with the `channelId` from `reply.arguments` and your answer as `content`. 2. Call `complete_run` with the `runId`: - `outcome: "completed"` closes the Run with your latest message in its conversation. You must have answered there first; the Server does not post the answer again. - `outcome: "failed"` closes the Run and posts your `note` as the reason. Only the Agent itself can complete its own Run. A later mention starts a new Run. ## Several bots from one account You can add several external Agents that belong to one outside account, such as several bots of one service. Give each its own Agent, webhook and access token, so messages and Runs are attributed correctly. A bot that answers through another bot's connection posts under that other Agent's name and cannot complete its own Run, which then fails at the timeout. > [!WARNING] > Bots of one account usually share connections and files on their side. Treat the account as one trust boundary: what one of them can read, all of them can. Use separate Roles for focus, not secrecy, and grant a private channel or Mailbox to one only if every bot of that account may see it. Deleting the Agent deletes its webhook and revokes its MCP connections and tokens. ## Related - [Agents overview](https://llll.gg/docs/agents/overview.md) - [Connect an MCP client](https://llll.gg/docs/agents/mcp.md) --- # Repositories and code work > Connect GitHub, add a repository environment and let L4 Agents take a Task to a draft pull request on a working branch. Source: https://llll.gg/docs/agents/repositories An Agent can work on code: it takes a Task, edits a copy of your repository in an isolated workspace, runs checks and opens a **draft pull request**. Merging and releasing stay with people. Agents never get merge or release tools. Code work needs five things. **Server settings → Agents** shows them as a checklist, **Set up agents that work on code**, until each is done: 1. **Connect a model.** See [Models and Providers](https://llll.gg/docs/agents/models-and-providers.md). 2. **Connect GitHub.** A dedicated GitHub App for the repositories Agents may work on. 3. **Choose a repository for Tasks.** An environment in **Git → Environments**. 4. **Add compute.** A Compute Node where the workspace runs. 5. **Create an agent** with a Role that has **Use a computer** and **Propose code changes**. [Screenshot: The Set up agents that work on code checklist in Server settings → Agents with five steps and their buttons (The code work checklist)] GitHub is the only Git provider that works today. GitLab and Gitea show **connections are not available yet**; Projects can record GitLab, Gitea and Cursor repositories as references only. ## 1. Connect GitHub L4 creates a private GitHub App for your Server, separate from any GitHub login. Only the Owner can set it up, and the Server needs an HTTPS address. 1. Open **Server settings → Git**. On the **GitHub** tab, select **Connect GitHub**. For an organization, select **Connect a GitHub organization** instead and enter its name. 2. GitHub opens in a new tab. Create the App, then choose which repositories it may access. 3. Return to L4. The connection appears in the list with the repositories it can access. Select the refresh button if it does not appear yet. [Screenshot: Server settings → Git with the GitHub tab, one connection and the Connect GitHub button (Connect GitHub)] The App asks for code and pull request write access, for working branches and draft pull requests, and read access to issues and checks. It does not ask for branch-protection administration. The App's key stays encrypted on your Server and is never given to an Agent. If GitHub asks you to sign in and then says it did not find an App Manifest, return to L4 and select **Prepare again after GitHub sign-in**. If you already created the App, refresh the connections instead of creating another. To manage a connection, open it: - **Change repositories on GitHub** changes which repositories the App can access. - **Disable in L4** stops L4 from using the connection at once. It does not uninstall the App; uninstall it on GitHub if you also want to remove its access there. ## 2. Add a repository environment An environment tells Tasks which repository, branch and compute to use. 1. In **Server settings → Git**, open the **Environments** tab and select **Add environment**. 2. Enter a **Name**, for example `Website fixes`. 3. Choose the **Git connection**, then the **Source repository**. Select **Refresh repositories** if the list is empty. 4. Choose the **Working repository**: the same repository for ordinary branch-based work, or an optional fork. 5. Choose the **Compute Node** where its Task Runs execute. 6. Enter the **Base branch**. 7. In **Required CI checks**, enter at least one exact check or commit-status name, one per line. A pull request whose required checks are missing or failing stays unverified. 8. Optionally list **Allowed Preview domains**, one per line, for preview links Agents may report. Preview links must use HTTPS. 9. Select **Save environment**. Saving changes to an environment stops Runs and Schedules that use its earlier version. ## 3. Add compute Code runs on a Compute Node, never on the Server itself. In **Server settings → Compute**, select **Add compute** to pair a Docker host or connect Lizard, or use **Server compute setup** for the option that fits where your Server runs. See [Compute Nodes](https://llll.gg/docs/self-host/compute-nodes.md). ## 4. Give the Agent and people the right permissions | Permission | Who needs it | | --- | --- | | Use a computer | Both the Agent and the person who starts the work | | Propose code changes | The Agent, to publish a branch and draft pull request | | Read tasks, Execute assigned tasks, Send messages | Both, to run a Task | | Read projects | Both, when the Task belongs to a Project | No existing Role gets **Use a computer** or **Propose code changes** automatically, not even Admin. The built-in **Developer** Project policy covers working on project Tasks and proposing code; assign it in **Projects → Access**. ## 5. Run a Task 1. Create a Task, assign it to the Agent and choose the **Repository environment**. 2. Select **Create and run**, or later **Run assigned agent** on the Task. 3. Follow the Run from your Inbox or the **Runs** page. The Agent downloads the repository at the base branch's exact commit, works in an isolated workspace and can run the checks available there. When it publishes, it pushes a branch named `l4-agent/` to the working repository and opens a draft pull request against the base branch. The Task moves to **Review**. Your existing CI, branch rules and preview deployments stay authoritative. A report from the Agent is not CI evidence. Agents cannot change `.github` or `.git`, force-push, change the base branch, merge, label a release or deploy. Internet access from workspace commands is limited, so package downloads and network-dependent tests may not work there. Rely on your repository's own CI. Per-Task workspace limits, such as actions, command runtime and draft PR updates, are set by the Owner in **Server settings → Agents → Execution**. ## Related - [Compute Nodes](https://llll.gg/docs/self-host/compute-nodes.md) - [Projects and Tasks](https://llll.gg/docs/guides/projects-and-tasks.md) - [Agents, Tasks and Runs](https://llll.gg/docs/concepts/agents-tasks-runs.md) --- # Schedules > Set one-time, daily or weekly reminders and recurring Agent work in L4, and recover a Schedule that is paused or blocked. Source: https://llll.gg/docs/agents/schedules A Schedule triggers a reminder or Agent work at a time you choose, once, daily or weekly. It runs under the permissions you had when you saved it, and those permissions are checked again before every occurrence. ## Who can use Schedules Schedules need **Manage schedules** in your Role. No Role has it by default, not even Admin. When you have it, **Schedule** appears in the rail. Recurring Agent work additionally needs **Execute assigned tasks** and **Use agents** for you, and **Execute assigned tasks** for the Agent. Repository work also needs **Use a computer** for both. ## Kinds of Schedule - **Reminder** posts `Reminder: …` to the chosen conversation as you, at most 1,800 characters. It stays unread for you and sends you a push notification, so it reminds you even though you wrote it. - **Run an assigned task** starts an Agent Run on a Task assigned to that Agent. It needs Agent execution configured on the Server and an active Agent. It stops by itself once the Task is done or archived. ## Create a Schedule 1. Select **Schedule** in the rail. 2. In **What should happen?**, write the reminder text or the instruction for the Agent. 3. Choose the **Action**: **Reminder** or **Run an assigned task**. 4. Choose the **Conversation**. For a task, also choose the **Assigned task**. 5. Enter the **First time** and choose the **Timezone**. The form previews the time in that zone and in yours. 6. Choose **Repeat**: **Once**, **Daily** or **Weekly**. 7. Select **Schedule**. [Screenshot: The Schedules page with the new schedule form showing What should happen?, Action, Conversation, First time, Repeat and Timezone, and the Schedule button (Create a Schedule)] The first time must be between five seconds and 366 days ahead. Daily and weekly repetitions keep the same wall-clock time across daylight saving changes. A time that falls in a daylight saving gap moves to the first valid time, and a repeated hour uses its first occurrence; the form warns about both. You can have 50 active Schedules, paused ones included. The form shows how many you use, for example `3 of 50 active`. ## Manage Schedules The list has **Current**, **Active**, **Paused**, **Blocked** and **History** filters. For each Schedule you can: - **Pause** it, and **Resume** it later. Missed repetitions are skipped, never replayed. A one-time Schedule whose time has passed runs at once when resumed. - **Edit** its text, next time, repeat or timezone, and for task work its Task. The action and conversation cannot change; cancel it and create a new one instead. Only the person who created a Schedule can edit it. - **Cancel** it. - **Remove** a completed or cancelled Schedule from your list. - Open **History** to see recent occurrences with links to the delivered message or started Run. Editing saves your current permissions as the Schedule's new ceiling. It never brings back a permission removed since you created it. While an occurrence is being delivered, you cannot change the time of an active Schedule; pause it first or wait until it finishes. ## When a Schedule fails or is blocked A temporary failure, such as a rate limit or the daily Run budget, is shown on the Schedule and retried after 30 seconds, 1 minute, 5 minutes, 15 minutes and 1 hour. After the last retry, the occurrence counts as missed: a repeating Schedule moves on to its next time, and a one-time Schedule becomes **blocked**. A Schedule is **blocked** at once when something it relies on is gone. The list shows the reason and a hint: | Reason | Cause | What to do | | --- | --- | --- | | `schedule_revoked` | A Role, resource grant or Member was reduced or removed. | Restore it, then **Resume**. | | `channel_unavailable` | The conversation was removed or no longer accepts your messages. | Restore access, then **Resume**, or create a new Schedule. | | `task_reassigned` | The Task was assigned to someone else. | Assign it back, then **Resume**. | | `agent_unavailable` | The Agent is paused or removed. | Activate it, then **Resume**. | | `pi_unconfigured` | Agent execution is not configured on the Server. | Ask the Server operator, then **Resume**. | | `code_denied`, `code_revoked` | The repository environment changed or computer permission was removed. | Create a new Schedule after reviewing it. | **Resume** checks again first, so it fails with the same reason until the cause is fixed. Editing a blocked Schedule makes it active again once the edit passes the same checks. Archiving a channel blocks Schedules that post there until the channel is restored. You can always cancel a Schedule, even after losing access to its conversation; it is then listed as "Conversation unavailable". ## Schedules created by Agents During a Run in a conversation, an Agent with **Manage schedules** can create, edit, list and cancel reminders in that conversation for you. Agents check the current Server time first and should confirm ambiguous times with you. A reminder an Agent created is the Agent's to edit. ## API Schedules also have an HTTP API under `/api/schedules` for signed-in Members with **Manage schedules**. See [docs/schedules.md](https://github.com/inchidev/llll/blob/main/docs/schedules.md#api). ## Related - [Agents overview](https://llll.gg/docs/agents/overview.md) - [Notifications](https://llll.gg/docs/guides/notifications.md) --- # Chat > Use channels, Threads, Direct Messages, mentions, files, search and the Inbox in an L4 Server. Source: https://llll.gg/docs/guides/chat Chat is where people and Agents in a Server talk. Everything here stays inside one Server: its channels, Direct Messages and files are not visible from other Servers, even to the same person. ## Channels Open **Chat** in the rail to see the Server's channels, grouped in categories. - An **open** channel can be read by every Member of the Server. - A **private** channel can be read only by its members. Its creator adds or removes people with **Manage channel members** in the channel header. People added to a private channel can read its whole history. Members with **Manage channels** create channels and categories from the Server menu (**Create Channel**, **Create Category**) or in **Server settings → Channels**. There they can also make a channel private or open, move it to another category and archive it. An **archived** channel leaves Chat and the Inbox and becomes read-only for everyone, its Threads included. Its history stays readable at its links and in search. Members with **Manage channels** restore it from the **Archived** tab; a private channel can be archived or restored only by its creator. ## Messages Type in the composer and press Enter to send. Shift+Enter adds a new line, and Arrow Up edits your last message. Messages can be up to 2,000 characters and support a small Markdown subset: bold, italic, code, links and lists. - **Files:** use **Upload a file** or drop files on the composer. Up to 8 files per message, each up to 10 MB, if your Role has **Attach files**. - **Voice messages:** use **Record audio**. A recording stops at 5 minutes. - **Reactions:** open a message's menu and choose **Add reaction**. Reactions are emoji only. Open a message's menu, or right-click it, for more actions: | Action | What it does | | --- | --- | | **Reply** | Replies to this message in the same conversation. | | **Create thread** / **Open thread** | Starts or opens a Thread on the message. | | **Pin message** | Pins it to the conversation, if you may pin there. | | **Edit message** | Changes your own message. | | **Create task** | Opens a new Task prefilled from the message; see [Projects and Tasks](https://llll.gg/docs/guides/projects-and-tasks.md). | | **Copy text**, **Copy message link**, **Copy message ID** | Copies them. | | **Delete message** | Deletes your own message, or any message in a channel you moderate. | [Screenshot: A chat message with its menu open showing Add reaction, Reply, Create thread, Pin message, Create task and Delete message (The message menu)] If your connection drops while a message is still sending, L4 keeps it and sends it when you reconnect. Reloading or closing the page at that moment asks you to confirm, because pending messages are not saved on disk. ## Threads A Thread is a child conversation started from one message, so a side discussion does not fill the main channel. Choose **Create thread** on a message to start one. Agents always reply in a Thread. ## Direct Messages Direct Messages are private conversations between Members of the same Server. Start one with the **New direct message** button next to **Direct messages** and search for the person by name, nickname or handle. - A one-to-one Direct Message stays between its two people. There is one per pair. - Including someone else starts a separate group with no copied history. - In a group, its creator can remove people and anyone can leave. A one-to-one Direct Message also has a **Call** button; see [Calls and Live](https://llll.gg/docs/guides/calls.md). For conversations with Agents, see the [Agents overview](https://llll.gg/docs/agents/overview.md#talking-to-an-agent). ## Mentions Type `@` in the composer to mention someone. | Mention | Notifies | | --- | --- | | `@name` | That person. A direct mention is also the only way to address an Agent. | | `@Role` | Everyone with that Role who can read the conversation, up to 500 people. | | `@channel` | Everyone who can read the conversation, up to 500 people. | | `@here` | Those of them who are online or idle, up to 500 people. | Anyone can mention a Role whose settings allow it (**Allow anyone to mention this role**). The Owner and Members with **Manage channels** can mention any Role and use `@channel` and `@here`; from anyone else they stay plain text. Role and group mentions never wake Agents and add nobody in a Direct Message. Editing a message notifies only people a new mention reaches. ## Search Press ⌘K (Ctrl+K on Windows and Linux), or select **Search** in the conversation header. Choose **This conversation** or **Whole server**. Results include archived channels. ## Inbox **Inbox** is the first rail section and the one place to catch up. It lists only what needs you: - **Approvals:** Agent actions from your Runs waiting for your decision. - **Mentions:** unread messages that mention you directly, through your Role or through `@channel` or `@here`. - **Agent work:** your Runs while queued or running and for three days after, with the draft pull request when one was opened. - **My tasks:** Tasks assigned to you, and Tasks you created that wait for your review. - **Mail:** Mailboxes you can read that have unread mail. - **Unread chats:** unread channels, Direct Messages and Agent chats. Mark one as read without opening it, or use **Mark all as read**. The Inbox badge in the rail counts only approvals and mentions, so ongoing work does not keep it lit. ## Related - [Notifications](https://llll.gg/docs/guides/notifications.md) - [Calls and Live](https://llll.gg/docs/guides/calls.md) - [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md) --- # Projects and Tasks > Organize work in L4 with Boards and Tasks, group it into Projects with their own access, and hand Tasks to Agents. Source: https://llll.gg/docs/guides/projects-and-tasks A **Task** records a piece of work and who is responsible for it. Tasks live on **Boards**. A **Project** groups Tasks and repository references and gives people and Agents access for that Project only. ## Boards Open **Tasks** in the rail. A Board collects the Tasks of one team or project in columns: **Backlog**, **Ready**, **Review**, **Done** and **Failed**. Members with **Manage tasks** create Boards with **Create board** next to **Boards**, and rename, archive or delete them. Archived Boards are listed under **Archived**. You need **Read tasks** to see Boards and **Create and update tasks** to add and edit Tasks. A Role can also be limited to specific Boards; see [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md). ## Create a Task 1. On a Board, select **Task** (the **New task** button). 2. Enter a **Title** and optionally a **Description**. 3. Choose an **Assignee**: a person or an Agent. 4. Optionally choose a **Project**, a **Priority** (Low, Normal, High, Urgent), a **Due** date, **Labels** and **Dependencies** on other Tasks of the Board. 5. Select **Create task**. [Screenshot: The Create task dialog with Title, Description, Assignee, Project, Report channel, Priority and Due fields and the Create and run and Create task buttons (Create a Task)] Open a Task to edit it, change its **Status** or **Board**, attach files, add **Checklist** items and set what it **Depends on**. **Archive task** hides it without deleting it. ### Create a Task from a message In any channel, Direct Message or Thread, open a message's menu and choose **Create task**. The form opens prefilled from the message, and the saved Task links back to it. People who can read the Task see the link on the message; people who cannot read the source conversation see only that the Task came from a message. Deleting the message keeps the Task. ### Discuss a Task Each Task has a **Discussion**, created when someone who can edit the Task starts it. It follows the Board's access: people who can read the Board's Tasks can read it. Agents working on the Task report in its Discussion unless a separate **Report channel** is set. ## Hand a Task to an Agent Assigning a Task to an Agent does not start any work; a Task assigned to an Agent waits in **Ready** until someone starts it. - When creating it, select **Create and run** instead of **Create task**. - On a saved Task, select **Run assigned agent** at the top. Starting work needs **Read tasks**, **Execute assigned tasks** and **Send messages** in the report conversation, for both the Agent and you. If something is missing, or the Agent is paused or the Task archived, the button explains why. Save pending edits before starting. For code work, also choose a **Repository environment**; see [Repositories and code work](https://llll.gg/docs/agents/repositories.md). When the Agent finishes, it records its result on the Task, moves it to **Review** and posts a report with the Task number, title, status and a link. You review the result and move the Task on. Follow progress in your Inbox under **Agent work** or on the **Runs** page. To repeat Task work on a timetable, see [Schedules](https://llll.gg/docs/agents/schedules.md). ## Projects Open **Projects** in the rail. You need **Read projects** to see Projects and **Manage projects** to create and configure them. No existing Role gets **Manage projects** automatically; the Owner always has it. ### Create a Project 1. Select **Create project**. 2. Enter a **Project name** and optionally a **Description**. 3. Save it, then assign access and attach repositories. Tasks keep their Board; choose the Project in the Task form. ### Project access A Project's access settings give people, Agents or a whole Server Role a reusable **Project policy** for that Project only. The same person or Agent can be a Developer in one Project and a Reviewer in another, while their Server Role stays the same. 1. Open the Project and find **Project access**. 2. Select **Assign access**. 3. Under **Assign to**, choose a person or Agent, or a Server Role, and choose the **Policy**. 4. Select **Save access**. Built-in policies include **Developer**, **Reviewer**, **Coordinator** and **Task coordinator**. You can create more in **Server settings → Roles & Policies → Policies**. Policies only add permissions; a read-only policy does not take away broader rights someone already has from their Server Role. ### Repositories in a Project Use **Attach repository** to record a repository by its **Git provider**, **Provider origin** (for example `https://github.com`) and **Repository path** (`organization/repository`). A reference alone connects no credentials and enables no work. To let Agents work on a GitHub repository, set it up in **Server settings → Git** and link the matching **Coding environment**; see [Repositories and code work](https://llll.gg/docs/agents/repositories.md). Project access never grants Git credentials, merging or releases. Archiving a Project stops Agent work in it. ## Tasks from MCP clients MCP clients approved for the Task scopes can list Boards and create and update Tasks on your behalf. See [Connect an MCP client](https://llll.gg/docs/agents/mcp.md). ## Related - [Agents, Tasks and Runs](https://llll.gg/docs/concepts/agents-tasks-runs.md) - [Chat](https://llll.gg/docs/guides/chat.md) --- # Mail > Read, reply to, forward and triage email in shared L4 Mailboxes, write internal notes and control who can use each Mailbox. Source: https://llll.gg/docs/guides/mail Mail lets a team share email addresses such as `support@example.com` inside the Server. Each **Mailbox** is a shared inbox with its own address and access rules. Incoming mail, replies and internal notes are grouped into **Email Conversations**. Mail is optional. Before anyone can use it, the Server needs an email domain and at least one Mailbox. On Cloudflare, the Owner connects a mail domain and creates Mailboxes as described in [Mail on Cloudflare](https://llll.gg/docs/cloudflare/mail.md). On other runtimes, email domains are managed by the Server operator. ## Who can use a Mailbox Two things decide access: - a **grant on the Mailbox** itself, and - **Read email** or **Send email** in the Member's Role. Members with **Manage server** set grants in **Server settings → Mail → Mailboxes**: open a Mailbox and, under **Access**, choose for each person **No access**, **Read only**, **Read and send** or **Manage**. Even the Owner needs a grant to see a Mailbox. [Screenshot: A Mailbox in Server settings → Mail with the Display name field, the Receive email switch and the Access list with per-member access levels (Mailbox access)] In the same place you can set the **Display name** shown as the sender name, and turn **Receive email** off to pause a Mailbox. A paused Mailbox accepts no new mail and is hidden from Mail; nothing is deleted. **Delete mailbox** removes the address and its conversations. ## Read mail Open **Mail** in the rail. Your Mailboxes are listed with their folders: **Inbox**, **Drafts**, **Sent**, **Archive**, **Spam** and **Trash**. - Search a folder by subject, address, sender name and the text of emails and internal notes. - Move a conversation with **Archive**, **Spam** or **Trash** in its header. Moving it changes it for everyone with the Mailbox, and needs a Read and send or Manage grant plus **Send email**. - New mail can reopen an archived conversation. Mail filed as Spam or Trash stays there. Your Inbox rail section lists Mailboxes with unread mail. Individual conversations stay in Mail. ## Send email You need a **Read and send** or **Manage** grant and **Send email** in your Role. 1. Select **Compose**. 2. Fill in **To** and **Subject**. Select **Add Cc/Bcc** for more recipients. Bcc recipients stay out of the conversation's participants. 3. Write the **Message**. The formatting toolbar adds bold, italic, links, lists and quotes. Plain text is sent as typed. 4. Optionally **Attach** files. 5. Select **Send email**. [Screenshot: The New email composer with To, Cc/Bcc, Subject and Message fields, the formatting toolbar and the Send email button (Write a new email)] ### Reply and forward In a conversation, the composer at the bottom has two modes: - **Reply** sends an email. It goes to the sender (or their Reply-To address) by default; switch **Reply to sender** to **Reply all** to include everyone in To and Cc. You can edit the recipients before sending. - **Internal note** adds a note for people who can open the Mailbox. It is never sent as email. **Forward** opens a new email that quotes the original and copies its attachments. ### Drafts The composer saves a draft a second after you stop typing. Drafts are yours and belong to one Mailbox; they are listed in **Drafts**. A reply draft reopens in its conversation. Sending removes the draft, and **Discard** deletes it with its uploads. ### Attachments - Up to 10 attachments, 3 MB in total, per email. - Files stay private to you until sent, then belong to the conversation. - Programs and scripts are refused. HTML, SVG and XML are sent as plain downloads. - Attachments need Server file storage. Without it, the composer explains that attachments are unavailable. Signatures are not supported yet. ## Delivery status Each email you send shows its status, such as **Sending**, **Sent** or **Not delivered**. If a send failed, use **Retry**. If L4 cannot tell whether the provider accepted it, it shows **Delivery unknown** and **Send again…**, and asks you to confirm before sending another copy, so you do not send a duplicate by accident. ## Notifications Each new email that lands in a Mailbox's Inbox sends a push notification to every person with a grant on that Mailbox and **Read email** or **Send email**, and to the Owner if they have a grant. Mail filed as Spam or Trash stays quiet. See [Notifications](https://llll.gg/docs/guides/notifications.md). ## Agents and MCP - MCP clients approved for the Mail scopes can list, search and read conversations and mark or move them, under the same grants. They cannot send email. See [Connect an MCP client](https://llll.gg/docs/agents/mcp.md#mail). - Sending external email always needs a human. In this release Agents cannot send email; only the Mail interface sends. - Email content comes from outside. Agents and clients treat it as untrusted data. ## Related - [Mail on Cloudflare](https://llll.gg/docs/cloudflare/mail.md) - [Inbox](https://llll.gg/docs/guides/chat.md#inbox) --- # Calls and Live > Start audio and video calls, Live sessions and screen sharing in L4 channels and Direct Messages, and connect Cloudflare RealtimeKit as the Owner. Source: https://llll.gg/docs/guides/calls Every channel and Direct Message has one Live room for audio, camera and screen sharing. In a one-to-one Direct Message it rings the other person as a **Call**; everywhere else it is **Live**, a room anyone in the conversation can join. Media runs on Cloudflare RealtimeKit in the Server's own Cloudflare account. Nothing is recorded. ## Start or join 1. Open the channel or Direct Message. 2. Select the phone button (**Call**) in a one-to-one Direct Message, or the waveform button (**Start live**) in a channel or group. 3. Check your **Microphone**, **Camera** and **Speaker** in the preview, then start. If a room is already running, the button says **Join call** or **Join live**, and a bar in the conversation offers to join. [Screenshot: A channel header with the Start live button and the Live preview dialog showing the Microphone, Camera and Speaker choices (Start a Live session)] Starting a room needs **Start live sessions** in your Role; the built-in Member Role has it. Anyone who can open the conversation can join. You cannot start a room in an archived channel or a Direct Message you can no longer write to. ## During a call - Mute or unmute, turn the camera on or off and share your screen from the controls. - Minimize the room to keep chatting; the Live bar stays visible. - **Leave** a Live session, or **Hang up** a call. If the connection drops, L4 reconnects and keeps your microphone and camera settings. The person who started a Live session, and in channels Members with **Manage channels**, can **Mute for everyone**, **Remove from live session** and **End for everyone**. In a one-to-one call both people have host controls. ## Incoming calls When someone calls you in a one-to-one Direct Message, L4 rings and shows **Answer**, **Decline** and **Ignore**. If you are already in another room, **Leave and answer** switches over. An unanswered call stops ringing after 45 seconds. Calls and newly started Live sessions also send push notifications; see [Notifications](https://llll.gg/docs/guides/notifications.md). ## Your audio settings In **User Settings → Voice & Video** you choose the **Input Device** and **Output Device**, and turn **Ring for incoming calls** on or off. Mute and deafen are also on your account panel. ## Connect Live (Owner) Calls need Cloudflare RealtimeKit. The Owner connects it in **Server settings → Live**. - **With kept Cloudflare access:** if the Server keeps Cloudflare access (see [Cloudflare access](https://llll.gg/docs/cloudflare/access.md)), select **Connect using this Server's Cloudflare access**. No separate token is needed. - **With an API token:** select **Create token**, create a Cloudflare API token with the **Realtime Admin** permission for the account the Server runs in, paste it and select **Connect**. If the token reaches several accounts, choose one. L4 then creates its own RealtimeKit app with separate host and participant presets. The token is stored encrypted. Use **Test connection** to check it, **Replace token** to change it and **Disconnect** to turn Live off. [Screenshot: Server settings → Live with the Connect using this Server's Cloudflare access button and the API token steps (Connect Live)] Messages you may see: | Message | Meaning | | --- | --- | | **Calls are unavailable on this runtime** | The Server cannot reach Cloudflare RealtimeKit, so calls stay off. Text chat is unaffected. | | **Calls use your Cloudflare API token** | The operator configured Cloudflare credentials for the deployment; the first call creates the RealtimeKit app. | | **Managed by the deployment** | RealtimeKit credentials come from the deployment's environment and override anything saved here. | | **The saved token can no longer be read** | The Server's `AUTH_SECRET` is missing or changed. Restore it, or paste a token to reconnect. | | **Everyone joins with host controls** | Live was connected before hosts and participants had separate permissions. Select **Reconnect**. | Cloudflare RealtimeKit usage is billed to your Cloudflare account. See [Costs and limits](https://llll.gg/docs/cloudflare/costs-and-limits.md). ## Related - [Chat](https://llll.gg/docs/guides/chat.md) - [Notifications](https://llll.gg/docs/guides/notifications.md) --- # Notifications > Turn on push notifications for an L4 Server on each device, and learn which mentions, messages, calls, mail and reminders notify you. Source: https://llll.gg/docs/guides/notifications L4 sends browser push notifications for things that need you. Notifications are set per Server and per device: turn them on in each browser or installed app where you want them. ## Turn on push notifications 1. Open the Server and select your avatar at the bottom of the rail to open **User Settings**. 2. Choose **Notifications**. 3. Next to **Push notifications**, select **Enable** and allow notifications when your browser asks. [Screenshot: User Settings → Notifications with the Push notifications row and its Enable button, and the Message sounds switch (Turn on push notifications)] Turn the switch off to stop notifications on this device. **Message sounds** plays a sound when a message arrives while the tab is in the background. If the row shows a badge instead of **Enable**: | Badge | Meaning | | --- | --- | | **Blocked** | Notifications are blocked for this site in your browser settings. Allow them there, then reopen the page. | | **Unavailable** | The Server has no Web Push configuration. Ask the Server operator. | | **Unsupported** | This browser does not support web push. | > [!TIP] > On iPhone and iPad, Apple allows web push only for sites added to the Home Screen. Open the Server in Safari, add it to the Home Screen, open it from there and then enable notifications. ## What notifies you | Event | Who is notified | | --- | --- | | A direct mention | The person mentioned, if they can read the conversation. | | A role mention, `@channel` or `@here` | The people it reaches who can read the conversation, up to 500. See [mentions](https://llll.gg/docs/guides/chat.md#mentions). | | A message in a Direct Message | Every other participant. | | A new Live session | Other Members who can open its channel or Direct Message. | | A call in a one-to-one Direct Message | The other person. | | New email in a Mailbox's Inbox | Everyone with a grant on the Mailbox and **Read email** or **Send email**. Spam and Trash stay quiet. | | A reminder you scheduled | You. The reminder also stays unread for you. | Ordinary channel messages that do not mention you do not send push notifications; they appear as unread in Chat and in your [Inbox](https://llll.gg/docs/guides/chat.md#inbox). Joining, leaving and ending a Live session do not notify anyone. People who cannot read a conversation are never notified about it. ## App badge When you install a Server as an app from its own address, each push also updates the app icon's badge with your count of unread conversations. Opening the app recalculates it. A message you read on another device updates the badge on the next push or launch. ## Notifications and the Shell Each Server sends its own notifications. Adding a Server to the [llll.gg Shell](https://llll.gg/docs/concepts/server-and-shell.md) does not change who is notified, and the Shell receives no message content. ## For operators A Server's Web Push identity must stay stable: if it changes, existing subscriptions stop working and people need to select **Enable** again. You can configure the identity for the deployment yourself (`VAPID_SUBJECT`, `VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY`, all three). Recent Servers without that configuration create their own identity when notification settings are first opened, provided the Server has an HTTPS address or an administrator email, and store it encrypted with `AUTH_SECRET`. Keep that secret with your [Backups](https://llll.gg/docs/cloudflare/backups.md). ## Related - [Chat](https://llll.gg/docs/guides/chat.md) - [Calls and Live](https://llll.gg/docs/guides/calls.md) - [Schedules](https://llll.gg/docs/agents/schedules.md) --- # Telegram > Open L4 Servers inside Telegram as a Mini App, sign in with Telegram, and what a Server operator must configure for it. Source: https://llll.gg/docs/guides/telegram L4 can run inside Telegram as a **Mini App**. The [llll.gg Shell](https://llll.gg/docs/concepts/server-and-shell.md) works there like it does in a browser: it shows the Servers saved on your device and embeds each Server's own Client. Each Server still keeps its own Accounts and sessions; Telegram never grants membership. ## Open a Server in Telegram When the Shell opens as a Telegram Mini App, it fits itself to Telegram's window and safe areas. A Mini App link whose start parameter is `server_` opens that Server directly. If the Server is not saved on the device yet, the Shell looks it up by its UUID and connects to it. A Server's own Client can also be opened as a Mini App. A start parameter of `invite_` then opens that invitation. ## Sign in with Telegram When you open a Server through the Shell in Telegram and that Server supports Telegram Mini App sign-in, the Shell passes your Telegram launch data to the Server and you are signed in automatically: - If your Telegram account is already linked to an Account on that Server, you are signed in to it. - If it is not linked and the Server's registration is open, a new Account is created for you from your Telegram name. - If registration is closed, sign-in is refused; use another login method or an invitation. The Telegram proof is checked by the Server itself and is valid for five minutes. If sign-in fails, close the Mini App and open it again. You can always sign in with the Server's ordinary login instead. Linking Telegram to an existing Account must be done explicitly from that Account; L4 never matches Accounts by name or email. > [!NOTE] > Telegram does not replace local Accounts. Each Server has its own Accounts, so signing in with Telegram on one Server does not sign you in to another. ## Telegram login in a browser Separately from the Mini App, the Owner can offer **Continue with Telegram** on the Server's sign-in page: 1. Open **Server settings → Authentication**. 2. Turn on **Enable Telegram** and enter its **Client ID** and **Client secret**. 3. Use the callback address shown there, `/api/auth/callback/telegram` on your Server, when you create those credentials. 4. Select **Save changes**. ## For operators: Mini App sign-in Mini App sign-in is off until the Server's deployment environment has one of: | Variable | Use | | --- | --- | | `TELEGRAM_BOT_TOKEN` | The token of a Telegram bot you own. The Server verifies Mini App data with it. | | `TELEGRAM_TRUSTED_BOT_ID` | The ID of a shared bot you explicitly trust. The Server verifies Telegram's public signature for that bot and never receives the bot's secret. | A Server with either one reports Telegram support in its public metadata, and the Shell then passes Telegram's launch data to that Server's Client. Without them, the Server's ordinary login still works inside Telegram. L4 has no guided setup for these variables yet; set them where you manage the deployment's other secrets. ## Related - [Server and Shell](https://llll.gg/docs/concepts/server-and-shell.md) - [Connect a Server](https://llll.gg/docs/get-started/connect-a-server.md) - [Notifications](https://llll.gg/docs/guides/notifications.md) --- # Glossary > The canonical L4 terms, from Server, Account and Member to Run, Shell and Backup, as they are used in the product and these docs. Source: https://llll.gg/docs/reference/glossary These are the terms L4 uses in its interface, code and documentation. The source is the project's [product language](https://github.com/inchidev/llll/blob/main/CONTEXT.md). ## L4 An MIT licensed workspace for humans and agents. People and Agents discuss work in a Server, organize it into Projects and Tasks, and work with connected repositories and shared information. Chat is the starting point for work. Mail, Data and Apps are capabilities inside the same Server. ## Server A community with its own Members, Roles, conversations, content and settings. A Member may belong to several Servers; access to one does not imply access to another. A Server owns its Accounts, Members and content. Its identity is a persistent UUIDv4. See [Servers and the Shell](https://llll.gg/docs/concepts/server-and-shell.md). ## Account A human's local login identity on one Server. Accounts on different Servers are independent. Explicit account linking connects login methods to an Account; matching email addresses never establish that link. ## Member A human or Agent taking part in one Server. A human's Account identifies them; their membership on that Server determines their access. An Account and a Member are different things. ## Owner The person who operates a Server's deployment. The Owner is created when the Server is first claimed and holds every permission. Owner rights do not grant membership of private channels. ## Role, Project policy and Grant - **Role:** each Member has one Server Role, which groups Server permissions. - **Project policy:** a reusable set of permissions for a person, an Agent or a Server Role in a particular Project. The same Member can have Developer access in Project A and Reviewer access in Project B. Policies add grants; they never remove broader rights a Server Role already gives. - **Grant:** permission for one operation on a resource, checked in its Project and resource scope. Project access does not grant Server administration or private-channel membership. See [Accounts, Members and Roles](https://llll.gg/docs/concepts/accounts-members-roles.md). ## Channel, Thread and Direct Message - **Channel:** a conversation in one Server. Channels are open or private. - **Thread:** a child conversation of a Channel or Direct Message. - **Direct Message (DM):** a private conversation between Members of the same Server. A 1:1 DM has two participants; including someone else starts a separate group with no copied history. Agent replies always go into threads. An **archived channel** leaves Chat navigation and the Inbox and becomes read-only for everyone, its Threads included: no messages, edits, reactions, pins, new Threads, Live rooms or Agent Runs. Its history stays readable at its links and in search. Members with Manage channels can archive and restore a channel; a private channel only its creator can. The last active channel cannot be archived. ## Mention A mention names who a message is for: - A **direct mention** names one Member. - A **role mention** names a Role's holders. - A **group mention**, `@channel` or `@here`, names everyone who can read the conversation, or those of them who are online. Only a direct mention addresses an Agent. ## Mailbox and Email Conversation A **Mailbox** is a shared inbox with its own access rules and email address. An **Email Conversation** groups incoming mail, outgoing replies and internal notes. Both belong to one Server. ## Inbox The Server rail's **Inbox** is one Member's queue of what is waiting on them: decisions on Runs they started, unread mentions, their Agent work, Tasks assigned to them or waiting for their review, unread mail in Mailboxes they can read, and unread chats. A Mailbox's Inbox folder is a different thing: the received email in one Mailbox. "Inbox" means the rail section; say "the Mailbox's Inbox folder" for mail. ## Agent A non-human Member with explicit permissions. An Agent can take part in conversations and use tools it is authorized for. It is not a login Account. Sending external email needs human approval. An **external Agent** works on its own computer, not with this Server's models: a Run wakes it through its webhook, and it answers through the Server's MCP as itself, within its Role. ## Session, Run, Environment and Schedule - **Session:** one Agent's context for a conversation. - **Run:** one attempt to carry out work. A Run may be linked to a Task. - **Environment:** the tools and working files available to a Run. - **Schedule:** triggers a reminder or work under a saved, revocable authorization. See [Agents, Tasks and Runs](https://llll.gg/docs/concepts/agents-tasks-runs.md). ## Task, Data and App - **Task:** records work and who is responsible for it. - **Data:** user-managed Databases, Views, Records, Documents and Canvases. - **App:** a published internal interface over explicitly authorized Data. These belong to one Server. Since 0.3.0, Data and Apps are paused and shown as coming soon; their stored data is unchanged. ## Project A Server-local record that groups Tasks and repository references. Project policies give people and Agents access within one Project. ## Compute Node A connected machine or provider that runs an Agent's repository workspace. See [Compute Nodes](https://llll.gg/docs/self-host/compute-nodes.md). ## Client and Shell - **Client:** the interface served by a standalone Server at its own address. - **Shell:** the official app at llll.gg that shows connected Servers and embeds their Clients. A saved connection in the Shell is a bookmark, not an Account or a membership grant. ## Address, primary address and alias A Server can answer at several public **addresses**. The **primary address** is used for new links, Shell display and background email. **Aliases** are additional addresses that reach the same Server UUID, Accounts and content. Every listed address is trusted, and the Shell recognizes one Server by its UUID even when it is added by a different address. An unlisted hostname is not a Server address. ## Provider and Backup - **Provider:** supplies an optional capability such as email delivery, inference or calls. - **Backup:** a recovery copy stored separately from live files. It is not a second running Server. See [Files and backups](https://llll.gg/docs/concepts/files-and-backups.md). ## Release A published, immutable, numbered version of L4. One shared version covers the Server, Shell, installer, runtime image and Docs. See [Releases and updates](https://llll.gg/docs/concepts/releases-and-updates.md). --- # Troubleshooting > Known L4 error messages and problems during install, setup, updates, Docker, mail, files and compute, with what causes them and how to fix them. Source: https://llll.gg/docs/reference/troubleshooting Find the message you see, or the symptom, and follow the fix. Error messages are quoted as the software prints them. ## Owner setup ### "This setup link is invalid or expired" The claim in the `/setup#claim=…` link does not match the Server's setup token. - **Cloudflare:** reconnect Cloudflare on the [Create page](https://llll.gg/create?tab=cloudflare), refresh your Servers and choose **Recover setup link**. Recovery works only while the Server has no Owner and no login Accounts. It never resets an existing Owner. - **Docker, Coolify or celld:** use the exact `SETUP_TOKEN` value of the running deployment. It must be a random string of at least 32 characters; a shorter value is ignored by owner setup. ### "Server setup is already complete" The Server already has an Owner. Sign in instead. A lost password can be reset with your [recovery key](https://llll.gg/docs/concepts/accounts-members-roles.md#account), if you saved one. ### Sign-in or setup fails on the address you opened `PUBLIC_ORIGIN` must be exactly the origin you open in the browser. Cookies, links and the setup claim depend on it. Additional hostnames must be listed as aliases (`LLLL_ORIGINS`, `llll domain add`, or **Server settings → Deployment → Domain** on Cloudflare). A hostname that only happens to reach the Server is rejected for sessions and changes. A request rejected with "Origin mismatch" came from an address that is not one of the Server's listed origins. The message names the address the request came from and the Server's address. If they match, the reverse proxy changed the Host or scheme on the way to the Server. ## VPS installer | Message | Fix | | --- | --- | | "VPS releases currently support Linux amd64 only; ARM64 local development is separate." | Use an amd64 VPS. | | "Supported VPS systems: Debian 12/13 and Ubuntu 22.04/24.04." | Reinstall the VPS with a supported system. | | "At least 2 GiB RAM is required." / "At least 5 GiB free disk space is required." | Use a larger VPS or free space. | | "Snap Docker is unsupported; install Docker Engine from its official apt repository." | Remove Snap Docker and install Docker Engine from Docker's apt repository, or let the installer do it on a clean machine. | | "Docker Engine >= 24 must be running on the local system socket." / "Docker Compose >= 2.24.4 is required." | Upgrade Docker yourself; the installer never replaces an existing Docker. | | "This is a prerelease. Review it and explicitly set LLLL_CHANNEL=prerelease." | Add `LLLL_CHANNEL=prerelease`, or use a stable release. | | "An existing Server has another version; use llll upgrade ." | Rerunning an installer only restarts the same version. Use `sudo llll upgrade`. | | "Downgrades or invalid versions are refused; restore a complete backup instead." | Choose a newer version, or restore a backup. | | "Another installer/CLI operation is running for this directory." | Wait for the other `llll` command to finish. | | "LLLL_DIR exists and is not an empty directory or a managed installation." | Choose an empty directory, or remove the unrelated files. | | "Scheduled updates/backups are not supported yet; use explicit llll upgrade and llll backup." | Remove `LLLL_AUTOUPDATE` and `LLLL_BACKUP_SCHEDULE`. | | "Choose another primary before removing this address." | Run `llll domain primary ` first. | | "The address change did not pass its checks; the previous configuration was restored." | Point the hostname's DNS at the VPS and make sure ports 80 and 443 are reachable, then retry. | | "Upgrade did not succeed; previous Server restored." | The old version is running again. Check `sudo llll logs` before retrying. | If an install stops before the Server is healthy, the private configuration is kept. Fix DNS, firewall or settings and rerun the same pinned script. Caddy needs correct DNS and reachable ports to get a certificate. ## Docker Compose and Coolify ### The container stops with "this data was migrated by L4 X; image Y is older" Migrations only go forward. Set an image at least as new as the version in the message (`LLLL_IMAGE` in Docker Compose, `LLLL_VERSION` in Coolify). To really go back, restore a backup. ### `bucket-init` fails with "could not reach or create s3://cells" RustFS is healthy but the host drops traffic between containers, typically legacy iptables with `-P FORWARD DROP`. Allow the Compose bridge, for example: ```bash iptables -I DOCKER-USER -i br- -j ACCEPT ``` Make the rule persistent. ### The `llll` container exits at start The entrypoint checks its configuration first: | Message | Fix | | --- | --- | | "llll: set AUTH_SECRET" (or another name) | Set the variable in `.env`. | | "llll: AUTH_SECRET must be at least 32 characters" | Use `openssl rand -hex 32`. The same rule applies to `SETUP_TOKEN`, `CLOUDFLARE_API_TOKEN` and the inbound mail secrets. | | "llll: CLOUDFLARE_ACCOUNT_ID must be 32 hex characters" | Copy the account ID from the Cloudflare dashboard, or leave it empty. | | "llll: ADMIN_EMAIL must look like an email address" | Fix `ADMIN_EMAIL`. | | "llll: RustFS did not answer at …" | Check that the `rustfs` service is healthy. | ### "waiting for the celld node to join" on first start This is expected. Migrations apply as soon as the node registers. The container stays unhealthy until they finish, and the gateway starts after that. ### The Server restarts after "node lease not renewed within TTL" A celld node that misses its lease renewal fences itself and exits. Compose and the Coolify template restart it automatically, after a short delay so the old lease can expire. If it happens often, check the machine's load and memory. ### Docker does not come back after a reboot On hosts without systemd (containers, some minimal images), start `dockerd` yourself and arrange for it to start at boot. Compose restart policies apply only once Docker is running. ### Settings says "Updates are managed outside L4" That is expected on a Docker or Coolify Server. Update by changing the image tag. See [Docker Compose](https://llll.gg/docs/self-host/docker.md#update). ## Cloudflare updates ### The update stops before deployment because the Server changed during backup The installer compares the backup with the live database and files. If they changed while the backup was made, it stops before uploading anything. Retry when the Server is less busy. ### "Deployment needs verification" A failure happened after the upload started. Other updates are blocked until it is resolved. Reopen the update from **Server settings → Deployment → Updates** (**Check or resume on llll.gg**). The installer checks the Server's identity and health, and reruns the upload only if the target version is not installed yet. ### Settings asks you to reconnect Cloudflare The Cloudflare access kept on the Server stopped working, for example because it was revoked. Reconnect it in **Server settings → Deployment → Updates**, or update through the official installer. ### A self-update fails on the Workers Free plan A Server updating itself uploads the release in one run, and the Free plan's subrequest budget is tight for large asset changes. This is a known limit. ### A 0.3.x release refuses the database 0.3.0 started the `server-v2` lineage. A 0.2.x database cannot be upgraded into it. Install a new Server for 0.3.x; the old one stays on its release. ## Files and secrets ### "File storage is unavailable. Ask the Server operator to configure storage." The Server has no file storage. On Cloudflare, connect R2 in **Server settings → Storage**. On self-hosted Servers, check the storage configuration. ### "Saved secret cannot be decrypted. Replace it." Backup settings show the same problem as "Saved Secret Access Key cannot be decrypted. Replace it." The Server's `AUTH_SECRET` changed since the secret was saved. Enter the credential again, or restore the original `AUTH_SECRET`. ## Mail ### Mail is rejected with "Unknown L4 mailbox" The recipient address has no Mailbox on this Server. Create the Mailbox first. Only enabled addresses in the Server's own Mailbox list receive mail. ### Inbound mail is not configured (self-hosted) Off Cloudflare, inbound mail needs a signed webhook: an Email Routing bridge (`INBOUND_EMAIL_SECRET`), Postmark, Mailgun or Amazon SES. Until one is set, the mail settings say inbound mail is not configured. Webhook timestamps more than five minutes from the Server's clock are rejected, so keep the clock in sync. See the [celld reference](https://github.com/inchidev/llll/blob/main/docs/self-host/celld.md#inbound-mail). ## Agents, calls and compute ### Agents cannot be created The Server has no model provider. Add one in **Server settings → Models**. On a self-hosted Server, a Cloudflare token or an OpenAI-compatible endpoint also works; see [Optional providers](https://llll.gg/docs/self-host/docker.md#optional-providers). ### The call button is disabled on a self-hosted Server Calls use Cloudflare RealtimeKit. Off Cloudflare they need `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`. Text chat keeps working without them. ### "No Compute Node is connected. Repository metadata remains available; coding needs a Node." Add compute in **Server settings → Compute**. See [Compute Nodes](https://llll.gg/docs/self-host/compute-nodes.md). ### Compute Node setup fails on a VPS | Message | Fix | | --- | --- | | "Compute Node needs 17 GiB free disk capacity for a 10 GiB Task workspace, its image and reserve for the Server." | Free disk space or use a larger disk. | | "Factory needs 2 GiB available RAM, including reserve for the Server." | Use a larger VPS. | | "Factory needs systemd as PID 1 (/run/systemd/system) before it writes executor files." | Add `--no-systemd` and start the Node yourself. | | "Docker shares the host kernel. Run llll factory enable --trust-shared-kernel only for trusted code/users." | Add `--trust-shared-kernel` if you trust the code and people on this VPS. | | "The released image changed. Existing executor and source remain pinned." | Export source, disable and revoke the Node in Settings, and pair a replacement for new Tasks. | If only the compute step fails during a new install, the Server stays installed and the script prints the command to retry. ## Shell ### A Server disappeared from My servers When a saved Server stops answering, llll.gg checks its address. Only Cloudflare's answer that no Worker serves that `workers.dev` address (error 1042) counts as deleted; the Shell then forgets it on this device and says so. Outages, timeouts and custom domains are never forgotten automatically. If the Server still exists, add it again from **Connect**.