# Zugoid documentation > Run your own Zugoid Server on Cloudflare or a VPS, bring your team and its AI agents into it, and connect MCP clients. Source: https://zugoid.com/docs Zugoid is a open-source workspace where people and AI Agents share channels, Projects and Tasks. Today each Server runs on its operator's own Cloudflare account or VPS and owns its Accounts, Members, Roles, content and files. [Zugoid Cloud](https://zugoid.com/pricing), where we host the Server for you, is coming. New here? Start with [What is Zugoid](https://zugoid.com/docs/get-started/what-is-zugoid.md), then [choose how to run it](https://zugoid.com/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://zugoid.com/llms.txt) describes Zugoid and links the main pages; [docs/llms.txt](https://zugoid.com/docs/llms.txt) indexes every page and [docs/llms-full.txt](https://zugoid.com/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://zugoid.com/docs/agents/mcp.md) or add it as an [external agent](https://zugoid.com/docs/agents/external-agents.md). ## Help and feedback Found a mistake or something missing? Open an issue on [GitHub](https://github.com/inchidev/zugoid/issues), or use **Edit on GitHub** in a page's menu. --- # What is Zugoid > Zugoid is open-source software for a workspace where people and Agents talk, plan and work together. Each organization runs its own Server. Source: https://zugoid.com/docs/get-started/what-is-zugoid Zugoid is a workspace for people and their Agents. An organization 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. Zugoid is independent, open-source software by [Inchi](https://inchi.dev), under the [Apache License 2.0](https://github.com/inchidev/zugoid/blob/main/LICENSE). You may use, change and run it free, including inside a company on your own infrastructure. Today every Server runs on its operator's own Cloudflare account or VPS, and the operator pays that provider directly. [Zugoid Cloud](https://zugoid.com/pricing), where we host the Server for you, is coming. ## What a Server contains A Server is one organization with its own: - **Accounts and login.** An Account is a person's username and password on that one Server, with email optional. 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 and Client 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. If you use several Servers, each Client can keep a list of the others in your browser. An entry is only an address. It holds no Account, no login and no membership. The official site at [zugoid.com](https://zugoid.com) hosts these docs and the `/create` installer. See [Servers](https://zugoid.com/docs/concepts/servers.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://zugoid.com/docs/agents/overview.md). ## Next steps - [Choose how to run your Server](https://zugoid.com/docs/get-started/choose-how-to-run.md) - [Create a Server on Cloudflare](https://zugoid.com/docs/get-started/create-on-cloudflare.md) - [Connect to an existing Server](https://zugoid.com/docs/get-started/connect-a-server.md) --- # Choose how to run your Server > Compare running an Zugoid Server on your Cloudflare account with running it on your own VPS, and pick the path that fits you. Source: https://zugoid.com/docs/get-started/choose-how-to-run Today every Zugoid 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://zugoid.com/docs/get-started/connect-a-server.md). ## At a glance | | Cloudflare | VPS | | --- | --- | --- | | How you install | In the browser at `/create`, tab **On Cloudflare** | One command on the VPS, from `/create`, tab **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 zugoid domain` on the VPS | | Updates | From Server Settings, or through the official installer | `sudo zugoid upgrade` on the VPS | | Backups | Weekly and before each update, to a separate R2 bucket | `sudo zugoid 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 [zugoid.com/create](https://zugoid.com/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 Zugoid 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://zugoid.com/docs/get-started/create-on-cloudflare.md). For the full picture, read the [Cloudflare overview](https://zugoid.com/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://zugoid.com/docs/get-started/install-on-a-vps.md). Docker Compose and Coolify are also possible; see [Docker](https://zugoid.com/docs/self-host/docker.md) and [Coolify](https://zugoid.com/docs/self-host/coolify.md). ## Zugoid Cloud (coming) With Zugoid Cloud we host your Server, and there is nothing for you to run. It is not available yet. The workspace is free there as everywhere; Zugoid sells only compute for Agents, by the hour of agent work. See [Pricing](https://zugoid.com/pricing). A Server on Zugoid Cloud can be asked for by an AI agent on your behalf, or made for you by a product built on Zugoid. In both cases the Server is yours: an agent never owns one. When an agent made it, you open the claim link it gives you, choose your username and password and become the Owner; the setup page tells you that an Agent, under the name the agent chose, will be on the Server, created by you under the **Cloud agent** Role (an admin that can connect and work), and you can change or remove it under **Agents** at any time. Until you claim, the Server answers nobody. When a product built on Zugoid made the Server, you open it from that product with **Open in Zugoid**: a one-time link that signs you in for that visit, good for ten minutes. You are the Owner there too, with no password until you set one in **Settings**, and the product's Agent is an ordinary Agent you can change. Nothing is linked by your email address. ## You can run more than one Each Server is independent. You can create several Servers, on either target, and [list them](https://zugoid.com/docs/get-started/connect-a-server.md) to move between them. A Member of one Server has no access to another. --- # Create a Server on Cloudflare > Install an Zugoid Server in your own Cloudflare account from the browser, claim it as Owner and open it. Source: https://zugoid.com/docs/get-started/create-on-cloudflare The official installer at [zugoid.com/create](https://zugoid.com/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. - 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://zugoid.com/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 [zugoid.com/create](https://zugoid.com/create) and choose the **On Cloudflare** tab. 2. Select **Connect Cloudflare**. 3. Sign in to Cloudflare and approve the requested permissions. [Cloudflare access](https://zugoid.com/docs/cloudflare/access.md) explains each one. 4. Back on the installer, choose the account under **Cloudflare account**. [Screenshot: The Create page with the 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: - **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://zugoid.com/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 form and the deployment fill the whole screen until the Server is deployed. 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 deployment ends, the installer shows a **Finish setup on your Server** button. If you select **Done** instead, the Server's row under **Your Servers** shows **Setup required** and the same 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. Choose a **Username** and a **Password**. **Your name** and **Email** are optional, and you can add an email later. 3. Select **Sign in as owner**. [Screenshot: The Server's Finish signing in page with Server name, Username, Your name, Email, 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. Nothing else can claim the Server. After you claim it, the claim is deleted. Once you have claimed the Server, its row shows **Open**. To reach it from another Server you use, see [Connect to a Server](https://zugoid.com/docs/get-started/connect-a-server.md). ### If you lost the setup link If you closed the tab or the installer session expired before you claimed the Server: 1. Open [zugoid.com/create](https://zugoid.com/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://zugoid.com/docs/get-started/invite-your-team.md) - [Add a custom domain](https://zugoid.com/docs/cloudflare/custom-domain.md) - [Connect a mail domain](https://zugoid.com/docs/cloudflare/mail.md) - [Create your first Agent](https://zugoid.com/docs/get-started/first-agent.md) - [Understand costs and limits](https://zugoid.com/docs/cloudflare/costs-and-limits.md) --- # Install on a VPS > Install an Zugoid Server on your own Debian or Ubuntu VPS with one command, then claim it as Owner. Source: https://zugoid.com/docs/get-started/install-on-a-vps The VPS installer runs one Zugoid 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://zugoid.com/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 [zugoid.com/create](https://zugoid.com/create?tab=vps) and choose the **On a VPS** tab. 2. Enter **Server domain**, for example `chat.example.com`. 3. Enter **Owner username**. 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://zugoid.com/docs/self-host/compute-nodes.md). 5. Select **Copy**. [Screenshot: The On a VPS tab with the Server domain and Owner username 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://zugoid.com/install.sh | sudo env \ ZUGOID_DOMAIN='chat.example.com' \ ZUGOID_ADMIN_USERNAME='you' \ 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/zugoid`, 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**. To reach it from another Server you use, see [Connect to a Server](https://zugoid.com/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 zugoid status sudo zugoid logs ``` ## Next steps - [Invite your team](https://zugoid.com/docs/get-started/invite-your-team.md) - [Back up, upgrade and add domains on the VPS](https://zugoid.com/docs/self-host/vps.md) --- # Connect to a Server > Open an Zugoid Server at its address or from an invitation link, sign in with that Server's own Account, and keep a list of your other Servers. Source: https://zugoid.com/docs/get-started/connect-a-server You use a Server by opening its address in a browser, for example `chat.example.com`. An invitation link opens the same Server. Everything works there: login, conversations, files and Settings. Each Server has its own Accounts. You sign in to each Server separately, with that Server's Account. ## Add another Server 1. Open a Server you use. 2. Select the Server's avatar at the top of the rail to open the Server menu. 3. Select **Add a Server**. 4. In **Address**, enter the other Server's address, for example `chat.example.com`. 5. Select **Add**. A rail of Servers then shows at the far left. On a phone, it appears with the navigation drawer or when you are signed out. Select a Server in the rail to go to it. This is an ordinary page load of another site. The rail shows each Server's name or initial, and no unread counts for other Servers. To hear from a Server while you are elsewhere, turn on [notifications](https://zugoid.com/docs/guides/notifications.md) there. ## Where the list is kept Your browser keeps the list on that Server's address, on this device. That Server's own page can read it. No other Server can. An entry holds an address and a public name. It is not an Account, holds no login and grants no membership. Lists are not synced. Each Server's list holds only what was added there or accepted there, and each device and browser has its own. ## Add the way back The first time you go to a listed Server, it learns where you came from and offers **Add the way back?** It shows the address first. Check that you recognize it, because anyone can write a link that offers an address. Select **Add** to put it on this Server's list, or **Not now** to leave the list as it is. Nothing is added without asking. ## Remove a Server from the list 1. Open the Server menu and select **Add a Server**. 2. Select the **×** next to the Server. Removing an entry does not delete the Server, your Account or your membership. You can add it again at any time. ## In the iOS, Android and Mac apps These apps are in the works and not published yet: today they are local builds from the source. Use a Server in your browser, on a phone too. The app keeps this device's list itself and shows it as a rail at the far left, on a phone together with the navigation drawer. **All Servers** in the Server menu opens the full list. A Server's page can't read it. ## Related - [Invite your team](https://zugoid.com/docs/get-started/invite-your-team.md) - [Servers](https://zugoid.com/docs/concepts/servers.md) --- # Invite your team > Create an invite link for your Zugoid Server, help people join with their own Account, and give them the right Role. Source: https://zugoid.com/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** and choose a username and password on your Server. Email is optional, and they can add it later. 3. After signing in, they select **Join**. If they use other Servers, they can add yours to the list on those. See [Connect to a Server](https://zugoid.com/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://zugoid.com/docs/concepts/accounts-members-roles.md). To let people talk to Agents, their Role needs **Use agents**. See [Create your first Agent](https://zugoid.com/docs/get-started/first-agent.md). ## When someone forgets their password A person with a verified email can reset their password from the sign-in page, if your Server can send mail. Everyone else asks you: 1. Open **Server settings → Members** and select the person. 2. Select **Create password reset link** and send them the link privately. The link works once and expires in a day. Creating and using it go in the audit log. You need the permission to remove members, and you can't create one for the Owner, who has a [recovery key](https://zugoid.com/docs/concepts/accounts-members-roles.md#account). ## 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://zugoid.com/docs/get-started/first-agent.md) - [Chat](https://zugoid.com/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 Zugoid Server. Source: https://zugoid.com/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://zugoid.com/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://zugoid.com/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://zugoid.com/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://zugoid.com/docs/agents/overview.md) - [Agents, Tasks and Runs](https://zugoid.com/docs/concepts/agents-tasks-runs.md) - [MCP](https://zugoid.com/docs/agents/mcp.md) --- # Zugoid on Cloudflare > What the Zugoid installer creates in your Cloudflare account, how the resources are named, and which settings manage them afterwards. Source: https://zugoid.com/docs/cloudflare/overview A Cloudflare Server runs entirely in your own Cloudflare account. The official installer at [zugoid.com/create](https://zugoid.com/create?tab=cloudflare) creates its resources through your Cloudflare authorization and uploads a published, checksum-verified release. There is no Zugoid 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://zugoid.com/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 | `zugoid-` | Runs the Server and serves its Client, with static assets. | | D1 database | `zugoid--db` | Accounts, Members, Roles, messages, Tasks, settings and the file index. | | Workers KV namespace | `zugoid--tickets` | Short-lived connection tickets. Also holds files when the Server uses basic file storage. | | R2 bucket (files) | `zugoid--files` | Private live files. Created when R2 is enabled. | | R2 bucket (backups) | `zugoid--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 | `zugoid-..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://zugoid.com/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://zugoid.com/docs/cloudflare/custom-domain.md) and [Mail](https://zugoid.com/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://zugoid.com/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://zugoid.com/docs/cloudflare/custom-domain.md). | | **Backups** | Download a backup or save one to an S3-compatible bucket. See [Backups](https://zugoid.com/docs/cloudflare/backups.md). | | **Updates** | Cloudflare access and updates. See [Updates](https://zugoid.com/docs/cloudflare/updates.md). | | **Remove** | How to remove the Server. See [Remove a Server](https://zugoid.com/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 Zugoid 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://zugoid.com/docs/cloudflare/access.md) - [Costs and limits](https://zugoid.com/docs/cloudflare/costs-and-limits.md) - [Updates](https://zugoid.com/docs/cloudflare/updates.md) --- # Cloudflare access > How Zugoid uses your Cloudflare authorization, what the OAuth permissions are for, and how to keep, replace or revoke the access a Server keeps. Source: https://zugoid.com/docs/cloudflare/access Zugoid 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 [zugoid.com/create](https://zugoid.com/create?tab=cloudflare), Cloudflare asks you to approve Zugoid's permissions. The resulting grant: - is encrypted inside your browser-bound installer session on zugoid.com; - 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 Zugoid 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); Zone settings (write); DNS (read, write); Email Routing rules (read, write); Email Sending (read, write) | Server addresses and mail domains. Zone settings is what turns Email Routing on for a domain. | | 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://zugoid.com/docs/cloudflare/updates.md). - **Server addresses.** Add, promote and remove custom domains. See [Custom domain](https://zugoid.com/docs/cloudflare/custom-domain.md). - **Mail domains.** Connect and disconnect mail domains. See [Mail](https://zugoid.com/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 Zugoid'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 `Zugoid 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, zone settings, 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 zugoid.com 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 cannot be connected from Settings until the Server keeps access again. --- # Costs and limits > Which Cloudflare products an Zugoid Server uses, who pays for them, and the limits that apply on Cloudflare. Source: https://zugoid.com/docs/cloudflare/costs-and-limits Zugoid is free, open-source software when you run it on your own infrastructure. A Cloudflare Server runs in your own account, and Cloudflare bills that account directly for what the Server uses. Zugoid 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 Service pricing](https://developers.cloudflare.com/email-service/platform/pricing/) | | 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. ### Box hours Each Task an Agent works on gets one container, its **box**. Containers are billed while they run, so the cost is box hours, not Tasks: - A box runs while its Agent works and stops after 10 minutes without a turn. A Task waiting for a merge or its next message costs nothing for its container. A turn waiting for your approval of a push or window keeps its box running until you decide or the Run ends. - At setup you choose a box size (small, standard or large) and how many boxes may run at once (4 by default, at most 64). That number is the container application's `max_instances`, so it caps how many boxes, and their hours, can add up at the same moment. A Task whose box does not fit waits until one stops. - Each box keeps its work in its own Artifacts repository, checkpointed when a turn ends. Artifacts are billed for storage and operations; a Task's repository is deleted when the Task is Done. - Each Run also has its own limits in **Server settings → Agents → Execution**, including box runtime per Task (six hours by default). 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. Zugoid 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://zugoid.com/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: - Sending email to anyone outside your Cloudflare account needs Workers Paid. This covers Mailboxes and reset and confirmation emails. - 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. ## Zugoid 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. | | Boxes at once | Chosen at setup, 4 by default, at most 64. | | Box size | Small 0.25 CPU, 1 GiB memory, 4 GB disk; standard 0.5 CPU, 4 GiB, 8 GB; large 1 CPU, 6 GiB, 12 GB. The disk is temporary. | ## 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 unless the Server sends email. - 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 Zugoid Server its own domain, choose the primary address, and add or remove aliases without breaking existing links. Source: https://zugoid.com/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 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, links, uploads, MCP and realtime. Adding an address never disables the others, and the `workers.dev` address always stays enabled. ## Before you start - The domain must be **active in the same Cloudflare account** as the Server. See [Get a domain](#get-a-domain). - The hostname must not already be attached to another Worker. - You must be the Owner. ## Get a domain Open [Domains in your Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/domains/overview), using the account that holds the Server. - **To buy a new domain,** register it there. Cloudflare charges the payment method on the account, and registrations are not refundable. The domain is active straight away. Check that auto-renew is on. - **To use a domain you already own,** select **Onboard a domain** and follow the steps. They end with changing the nameservers at your current registrar. Wait until Cloudflare shows the domain as **Active**, usually a few minutes and up to a day. When the Server keeps Cloudflare access, the **Server address** page lists the active domains in the account and links to this page when there are none. ## Add an address 1. In your Server, open **Server settings → Deployment → Domain**. The page is titled **Server address**. 2. Select **Add address**. 3. If the page lists **Cloudflare domain**, choose the domain. In **Domain**, keep it or put a subdomain in front, 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 zugoid.com; 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 and background email then use the new primary. People using an older address keep reaching the Server. ## 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, browser bookmarks, Server list entries 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://zugoid.com/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://zugoid.com/docs/cloudflare/mail.md) - [Servers](https://zugoid.com/docs/concepts/servers.md) --- # Mail on Cloudflare > Connect a mail domain to your Cloudflare Zugoid Server with Cloudflare Email Routing and Email Sending, then create shared Mailboxes. Source: https://zugoid.com/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://zugoid.com/docs/guides/mail.md). ## Before you start - The Server must **keep Cloudflare access**. The installer's OAuth grant has every permission mail needs. The pre-filled API token for updates does not. See [Cloudflare access](https://zugoid.com/docs/cloudflare/access.md). - The domain must be active in the same Cloudflare account as the Server. See [Get a domain](https://zugoid.com/docs/cloudflare/custom-domain.md#get-a-domain). - To send email to anyone outside your Cloudflare account, the account needs the **Workers Paid** plan. See [Costs and limits](https://zugoid.com/docs/cloudflare/costs-and-limits.md#free-and-paid-workers-plans). - You must be the Owner. Without kept access, the Domains tab asks you to keep it before you connect a domain. > [!WARNING] > Connecting a domain routes its mail to this Server. If the domain already receives mail through another provider, Zugoid refuses it and leaves that mail alone. Use a subdomain such as `mail.example.com` instead. ## 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 domain**. 4. In **Mail domain**, keep the domain or enter a subdomain, for example `mail.example.com`. 5. Select **Connect**. The form for your first Mailbox opens next. [Screenshot: The Domains tab in Mail settings with the Connect domain form showing the Cloudflare domain select, the Mail domain field and the Connect button (Connect a mail domain)] The Server then: - enables Cloudflare Email Routing for the domain, unless it already routes through Cloudflare; - onboards the domain for Cloudflare Email Sending, or re-enables it if it was turned off; - adds the Email Sending binding to its Worker; - starts sending reset and confirmation emails from `no-reply` at the domain. To use another address, change **Sender address** in **Server settings → Authentication**. You do not add any DNS records yourself. If a domain shows **Receives mail, sending is off**, Cloudflare has not enabled sending yet. Select **Finish connecting** to try again. **Sending is not set up yet** at the top of the page means the same until the domain finishes connecting. ### If Cloudflare did not turn on Email Routing Access kept from an older release lacks the permission to turn Email Routing on, so **Connect** stops with this message. Do one of these, then select **Connect** again: - Update the Server through the [official installer](https://zugoid.com/docs/cloudflare/updates.md) with **Keep Cloudflare access on this Server** checked, which replaces the kept access. - In your Cloudflare dashboard, go to **Compute → Email Service → Email Routing**, select **Onboard Domain** and choose the domain. ## 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**. The Owner and the person who created it get **Manage**. 4. Open the Mailbox to set its **Display name** and choose who else has access. The Server creates a Cloudflare Email Routing rule that sends the address to it. If the address already has a rule for something else, the Mailbox is not created until you remove that rule. Deleting a Mailbox removes its rule. Each Mailbox has its own access rules. People need a grant on the Mailbox plus **Read email** or **Send email**. To check it works, send an email to the new address from your personal mail, then reply to it from **Mail** in Zugoid. ## Disconnect a mail domain On the **Domains** tab, select **Disconnect** next to the domain. - If Zugoid 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. - Mailboxes are read and answered in Zugoid. There is no IMAP access for mail apps. - A new Cloudflare account starts with a low daily sending limit, and mail from a new domain may land in spam until the domain builds a reputation. Cloudflare's own limits and pricing apply. See [Email Service limits](https://developers.cloudflare.com/email-service/platform/limits/) and [pricing](https://developers.cloudflare.com/email-service/platform/pricing/) in the Cloudflare docs. ## Related - [Mail guide](https://zugoid.com/docs/guides/mail.md) - [Custom domain](https://zugoid.com/docs/cloudflare/custom-domain.md) --- # Update a Cloudflare Server > Update your Cloudflare Zugoid Server to a newer published release from its Settings, with a verified backup first, with or without the installer. Source: https://zugoid.com/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 zugoid.com, 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 zugoid.com**. 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://zugoid.com/docs/cloudflare/backups.md). --- # Backups on Cloudflare > How a Cloudflare Zugoid Server backs up its database and files, where backups are kept, and how to make one yourself. Source: https://zugoid.com/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. Zugoid never deletes backup buckets together with a Server. ## Automatic backups When R2 is enabled, the installer creates a separate backups bucket named `zugoid--backups`, next to the files bucket `zugoid--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://zugoid.com/docs/cloudflare/updates.md). Archives are stored under `servers//` in the bucket, named by the time they were made. Zugoid 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 Zugoid 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://zugoid.com/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://zugoid.com/docs/concepts/files-and-backups.md). --- # Remove a Cloudflare Server > Remove an Zugoid Server from your Cloudflare account safely, keeping its backups, and clean up domains, mail and kept access. Source: https://zugoid.com/docs/cloudflare/remove-a-server Zugoid 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, `zugoid-`. Every resource of this Server uses the same UUID: | Resource | Name | Remove? | | --- | --- | --- | | Worker | `zugoid-` | Yes | | D1 database | `zugoid--db` | Yes | | KV namespace | `zugoid--tickets` | Yes | | R2 files bucket | `zugoid--files` | Yes, if it exists | | R2 backups bucket | `zugoid--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://zugoid.com/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 `zugoid-`. 2. Delete the D1 database `zugoid--db`. 3. Delete the KV namespace `zugoid--tickets`. 4. Delete the R2 bucket `zugoid--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 `zugoid--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 Zugoid's authorization in your Cloudflare profile. - If you connected an API token, delete that token in the Cloudflare dashboard. ## 6. Remove it from Server lists Other Servers you use may still list this one, on each device. Open the Server menu there, select **Add a Server** and select the **×** next to it. In `/create`, select **Remove from 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 Zugoid VPS installer and the zugoid command, from install and addresses to backups, upgrades, restore and uninstall. Source: https://zugoid.com/docs/self-host/vps The VPS installer runs one standalone Zugoid 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://zugoid.com/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://zugoid.com/docs/self-host/docker.md) instead. ## Install Run as root on the VPS: ```bash curl -fsSL https://zugoid.com/install.sh | sudo env \ ZUGOID_DOMAIN=chat.example.com \ ZUGOID_ADMIN_USERNAME=owner \ bash ``` The **VPS** tab on the [Create page](https://zugoid.com/create?tab=vps) builds the same command from your domain and Owner username. 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/zugoid/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 ZUGOID_DOMAIN=chat.example.com ZUGOID_ADMIN_USERNAME=owner bash install.sh ``` For a prerelease (alpha, beta or rc), add `ZUGOID_CHANNEL=prerelease`. Checksums and provenance come from GitHub over HTTPS; releases are not cryptographically signed. ### What the installer creates - `/opt/zugoid`, mode 0700, with the private `.env` (mode 0600). - A private copy of the CLI and `/usr/local/bin/zugoid`. - 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 `zugoid 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 zugoid status sudo zugoid logs sudo zugoid stop sudo zugoid start sudo zugoid backup sudo zugoid 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 zugoid domain list sudo zugoid domain add chat.example.com sudo zugoid domain primary chat.example.com sudo zugoid domain remove old.example.com ``` To add an address: 1. Point the new hostname's DNS at the VPS. 2. Run `sudo zugoid domain add `. The command adds the origin to `ZUGOID_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 zugoid 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/zugoid/`. The latest seven are kept; set `ZUGOID_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 `zugoid backup` yourself or from your own scheduler. ## Upgrade ```bash sudo zugoid upgrade # latest stable release sudo env ZUGOID_CHANNEL=prerelease zugoid 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 `zugoid 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 ZUGOID_DIR=/opt/zugoid bash install.sh restore /private/zugoid-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 zugoid uninstall # confirms the project and backs up first sudo zugoid 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, `zugoid 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 | | --- | --- | | `ZUGOID_DOMAIN` | Required on first install. Domain without scheme or path. | | `ZUGOID_ADMIN_USERNAME` | Username of the first Owner. Defaults to `owner`. | | `ZUGOID_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`, `ZUGOID_ORIGINS` | Primary origin and comma-separated aliases. Managed by `zugoid domain`; do not edit by hand. | | `ZUGOID_DIR` | `/opt/zugoid`. Use the same value with every CLI command if you change it. | | `ZUGOID_CHANNEL` | `stable`. Set `prerelease` to install or upgrade to an alpha, beta or rc. | | `ZUGOID_ACME_EMAIL` | Optional. Contact for Caddy's ACME account, for certificate notices. | | `ZUGOID_HTTP_PORT`, `ZUGOID_HTTPS_PORT` | `80` and `443`. Custom ports need public ACME challenge ports forwarded. | | `ZUGOID_NO_TLS` | `false`. `true` serves plain HTTP, for isolated tests only. | | `ZUGOID_SKIP_DOCKER_INSTALL` | `false`. `true` requires an existing local Docker Engine and Compose. | | `ZUGOID_BACKUP_DIR` | `/var/backups/zugoid/`. Must be outside `ZUGOID_DIR`. | | `ZUGOID_BACKUP_KEEP` | `7`. | | `ZUGOID_FACTORY`, `ZUGOID_SHARED_KERNEL_TRUSTED` | Set both (`true`, `1`) to add a [Compute Node](https://zugoid.com/docs/self-host/compute-nodes.md) during install. | The version is chosen by the installer or `zugoid 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/zugoid/.env` using the keys listed in [`self-host/.env.example`](https://github.com/inchidev/zugoid/blob/main/self-host/.env.example), then run `sudo zugoid start`. See [Providers off Cloudflare](https://zugoid.com/docs/self-host/docker.md#optional-providers) for what each key does. ## Related - [Docker Compose](https://zugoid.com/docs/self-host/docker.md) for a manual stack behind your own proxy. - [Compute Nodes](https://zugoid.com/docs/self-host/compute-nodes.md) to let Agents work on repositories on this VPS. - [Releases and updates](https://zugoid.com/docs/concepts/releases-and-updates.md). - [Troubleshooting](https://zugoid.com/docs/reference/troubleshooting.md). --- # Run a Server with Docker Compose > Run one Zugoid Server with Docker Compose behind your own reverse proxy, then update it by changing the image tag. Source: https://zugoid.com/docs/self-host/docker The common Docker Compose stack runs one standalone Zugoid 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://zugoid.com/docs/self-host/vps.md). For Coolify, see [Coolify](https://zugoid.com/docs/self-host/coolify.md). ## What runs | Service | Role | | --- | --- | | `zugoid` | 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/zugoid:`, `:` (for example `0.3`) and `:latest`, for `linux/amd64` and `linux/arm64`. Prereleases are not published as images. - **Release archive.** Each release includes `zugoid-runtime-linux-amd64.tar.gz` and `SHA256SUMS`. Verify the checksum, then `docker load --input zugoid-runtime-linux-amd64.tar.gz`. This loads `zugoid:` locally. The common Compose file uses `pull_policy: never`, so pull the published image yourself before starting: ```bash docker pull ghcr.io/inchidev/zugoid: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/zugoid.git cd zugoid/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_USERNAME` | Suggested username of the first Owner. Defaults to `owner`. | | `RUSTFS_ACCESS_KEY`, `RUSTFS_SECRET_KEY` | Keys for the private bucket. | | `ZUGOID_IMAGE` | `ghcr.io/inchidev/zugoid:` or `zugoid:`. | 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_USERNAME` and that password, then change it. The `zugoid` 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; } } ``` `ZUGOID_HTTP_PORT` changes the host port. `ZUGOID_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 `ZUGOID_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://zugoid.com/docs/concepts/servers.md#addresses). ## Update Updates are an image tag. Zugoid Settings does not update a Docker Server; **Server settings → Deployment → Updates** says that updates are managed outside Zugoid and shows when a newer stable release exists. 1. Take and verify a backup (below). 2. Pull or load the new image and set `ZUGOID_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 zugoid: this data was migrated by Zugoid 0.0.2; image 0.0.1 is older. Use 0.0.2 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 `zugoid`: ```bash docker compose stop docker run --rm -v zugoid_rustfs-data:/from -v "$PWD":/to alpine:3.24 \ tar -C /from -czf /to/rustfs-data.tgz . docker run --rm -v zugoid_celld-data:/from -v "$PWD":/to alpine:3.24 \ 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://zugoid.com/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. | You don't need to set any of these by hand. The Owner can enter search, page reading, Cloudflare and mail settings in **Server settings → Integrations**. A variable set here wins over the same setting there, and the screen shows it as set by the deployment. `/api/setup/health` reports which provider the Server chose. The [celld reference](https://github.com/inchidev/zugoid/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 Server people rely on. --- # Run a Server on Coolify > Deploy one Zugoid Server in Coolify from the published image, claim the Owner, add addresses and update by redeploying or pinning a version. Source: https://zugoid.com/docs/self-host/coolify The Coolify template runs one standalone Zugoid Server: Zugoid 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/zugoid` image. It is public, so Coolify pulls it without a registry login and nothing is built on your machine. It is not a published Coolify catalog entry yet. Copy the Compose file below. ## Install 1. In Coolify, choose **New Resource → Docker Compose** and paste the [Compose file](#compose-file). 2. Set the domain of the `zugoid` service, on port `8080`. Set `ADMIN_USERNAME` to the first Owner's username; it defaults to `owner`. Leave `ZUGOID_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. Sign in with `ADMIN_USERNAME` and the password Coolify generated: `SERVICE_PASSWORD_ADMIN` in the service's environment variables. Then change the password in your account settings. The first start creates the Owner from these values. Later changes to `SERVICE_PASSWORD_ADMIN` have no effect. To choose the password yourself, set `SERVICE_PASSWORD_ADMIN` (at least 12 characters) before the first deploy. If the Server does not come up, read the logs of the `zugoid` container in Coolify; see also [Troubleshooting](https://zugoid.com/docs/reference/troubleshooting.md). Coolify generates these values for the template: | Variable | Used as | | --- | --- | | `SERVICE_URL_ZUGOID_8080`, `SERVICE_FQDN_ZUGOID_8080` | The Server's public origin (`PUBLIC_ORIGIN`). | | `SERVICE_PASSWORD_64_AUTHSECRET` | `AUTH_SECRET`. Signs sessions and encrypts stored credentials. | | `SERVICE_PASSWORD_ADMIN` | `ADMIN_PASSWORD`. The first Owner's initial password. | | `SERVICE_PASSWORD_64_SETUPTOKEN` | `SETUP_TOKEN`. Only used if the Owner is claimed on `/setup` instead. | | `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 `Zugoid`). You can rename the Server later in Settings. ## Compose file This is [`self-host/coolify/docker-compose.yaml`](https://github.com/inchidev/zugoid/blob/main/self-host/coolify/docker-compose.yaml) as of this release. Use **Copy** and paste it into Coolify unchanged. ```yaml # documentation: https://github.com/inchidev/zugoid/blob/main/self-host/coolify/README.md # slogan: Self-hosted team chat on a single VPS. # category: collaboration # tags: chat,team,celld,rustfs,self-host,zugoid # logo: svgs/zugoid.png # port: 8080 services: # Existing installations used a root-owned volume. Prepare it before the # unprivileged node starts, including after a cold restore. celld-perms: image: alpine:3.24 user: "0:0" restart: "no" network_mode: none read_only: true cap_drop: [ALL] cap_add: [CHOWN, DAC_OVERRIDE, FOWNER] security_opt: [no-new-privileges:true] volumes: - celld-data:/data command: ["chown", "-R", "10001:10001", "/data"] rustfs-perms: image: alpine:3.24 user: "0:0" restart: "no" volumes: - rustfs-data:/data command: - sh - -c - chown -R 10001:10001 /data rustfs: image: rustfs/rustfs:1.0.4 restart: always depends_on: rustfs-perms: condition: service_completed_successfully environment: - RUSTFS_VOLUMES=/data - RUSTFS_ADDRESS=0.0.0.0:9000 - RUSTFS_CONSOLE_ENABLE=false - RUSTFS_ACCESS_KEY=$SERVICE_USER_RUSTFS - RUSTFS_SECRET_KEY=$SERVICE_PASSWORD_64_RUSTFS - RUSTFS_OBS_LOGGER_LEVEL=error volumes: - rustfs-data:/data healthcheck: test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9000/health"] interval: 10s timeout: 5s retries: 12 start_period: 20s bucket-init: image: amazon/aws-cli:2.39.3 restart: "no" depends_on: rustfs: condition: service_healthy environment: - AWS_ACCESS_KEY_ID=$SERVICE_USER_RUSTFS - AWS_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_64_RUSTFS - AWS_DEFAULT_REGION=${AWS_REGION:-us-east-1} - AWS_EC2_METADATA_DISABLED=true entrypoint: ["/bin/sh", "-c"] command: - | set -eu aws configure set default.s3.addressing_style path i=0 while [ "$$i" -lt 30 ]; do if aws s3api head-bucket --bucket cells --endpoint-url http://rustfs:9000 >/dev/null 2>&1; then exit 0 fi if aws s3api create-bucket --bucket cells --endpoint-url http://rustfs:9000 --region us-east-1; then exit 0 fi i=$$((i + 1)) sleep 2 done echo "could not create s3://cells" >&2 exit 1 # Updates are this image tag: latest follows stable releases, or pin a # version such as 0.0.2 and change it to update. Never set an older one. zugoid: image: ghcr.io/inchidev/zugoid:${ZUGOID_VERSION:-latest} # A celld node that loses its lease exits; Docker starts it again. restart: always user: "10001:10001" read_only: true cap_drop: [ALL] security_opt: [no-new-privileges:true] tmpfs: - /tmp:size=256m,mode=1777 stop_grace_period: 50s depends_on: celld-perms: condition: service_completed_successfully bucket-init: condition: service_completed_successfully rustfs: condition: service_healthy environment: - SERVICE_URL_ZUGOID_8080 - SERVICE_FQDN_ZUGOID_8080 - PUBLIC_ORIGIN=$SERVICE_URL_ZUGOID_8080 - ZUGOID_ORIGINS=${ZUGOID_ORIGINS:-} - AUTH_SECRET=$SERVICE_PASSWORD_64_AUTHSECRET - SETUP_TOKEN=$SERVICE_PASSWORD_64_SETUPTOKEN - ADMIN_USERNAME=${ADMIN_USERNAME:-owner} # Coolify generates the first Owner's password; the first start creates the Owner. - ADMIN_PASSWORD=$SERVICE_PASSWORD_ADMIN - APP_NAME=${APP_NAME:-Zugoid} - AWS_ACCESS_KEY_ID=$SERVICE_USER_RUSTFS - AWS_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_64_RUSTFS - AWS_REGION=${AWS_REGION:-us-east-1} - AWS_EC2_METADATA_DISABLED=true - S3_ENDPOINT=http://rustfs:9000 - CELLD_BUCKET=s3://cells - CELLD_WATCH=/var/lib/zugoid/celld - CELLD_DURABILITY=bucket - CELLD_READY_FLEET_GATE_MS=0 - CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID:-} - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:-} - AI_BASE_URL=${AI_BASE_URL:-} - AI_API_KEY=${AI_API_KEY:-} - AI_MODEL=${AI_MODEL:-} - MAIL_GATEWAY_URL=${MAIL_GATEWAY_URL:-} - MAIL_GATEWAY_TOKEN=${MAIL_GATEWAY_TOKEN:-} - SMTP_GATEWAY_URL=${SMTP_GATEWAY_URL:-} - SMTP_GATEWAY_TOKEN=${SMTP_GATEWAY_TOKEN:-} - ZUGOID_EMAIL_DOMAINS=${ZUGOID_EMAIL_DOMAINS:-[]} - MAIL_APP_HOSTNAME=${MAIL_APP_HOSTNAME:-} - MAIL_DEFAULT_LOCAL_PART=${MAIL_DEFAULT_LOCAL_PART:-} - INBOUND_EMAIL_SECRET=${INBOUND_EMAIL_SECRET:-} - BROWSER_CDP_URL=${BROWSER_CDP_URL:-} volumes: - celld-data:/var/lib/zugoid/celld healthcheck: test: ["CMD", "/usr/local/bin/zugoid-healthcheck"] interval: 15s timeout: 5s retries: 12 start_period: 240s volumes: rustfs-data: celld-data: ``` ## More addresses To serve more domains, add them in Coolify and list every origin in `ZUGOID_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 Zugoid: - **Follow stable releases.** Leave `ZUGOID_VERSION` unset to use `latest`. Redeploy the service to pull the newest stable release. - **Pin a version.** Set `ZUGOID_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 Zugoid, **Server settings → Deployment → Updates** shows "Updates are managed outside Zugoid" and tells you when a newer stable release exists. [Screenshot: Server settings → Deployment → Updates on a Docker Server, showing the "Updates are managed outside Zugoid" notice and a newer release link (Docker and Coolify Servers update outside Zugoid)] ## 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://zugoid.com/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`, `ZUGOID_EMAIL_DOMAINS`, `MAIL_APP_HOSTNAME`, `MAIL_DEFAULT_LOCAL_PART`, `INBOUND_EMAIL_SECRET` and `BROWSER_CDP_URL`. See [Optional providers](https://zugoid.com/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 own VPS, in your own Cloudflare account or with Zugoid Compute by the hour. Source: https://zugoid.com/docs/self-host/compute-nodes A Compute Node is where an Agent's repository work runs. Each repository-backed Task gets one **box** on a Node: a container with a Git checkout where the Agent edits, runs commands and commits on its own, without asking the Server for each command. The model's keys stay on the Server; the box reaches the model through it. Without a connected Node, repository metadata is still available, but Agents cannot run code. This page covers setting up compute. For how Agents use it, see [Repositories](https://zugoid.com/docs/agents/repositories.md). ## Choose a kind of compute Open **Server settings → Compute** (requires Manage server). **Add compute** offers three paths: **Your own VPS** (Connect node), **Your own Cloudflare** (Server compute setup) and **Zugoid Compute** (Add Zugoid Compute). The **Server compute setup** entry shows the option that fits where your Server runs. The Server speaks one Node protocol to all of them; only where the Node runs differs. | Option | Where it runs | Good for | | --- | --- | --- | | VPS local executor | Docker on the same VPS as a [VPS installer](https://zugoid.com/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. | | Remote Docker Node | Any Linux Docker host you pair | Advanced setups, and any machine you rent yourself. | | Zugoid Compute | A box per Task from zugoid.com, paid by the hour of agent work | Compute without running a machine yourself, however many Agents work at once. Available on every Server once linked. | The workspace is free wherever you run it. Zugoid sells only Managed Compute; see [Pricing](https://zugoid.com/pricing). > [!WARNING] > Docker boxes 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 `ZUGOID_FACTORY=true ZUGOID_SHARED_KERNEL_TRUSTED=1`, or later: ```bash sudo zugoid compute enable --trust-shared-kernel ``` `zugoid factory` is the same command. The CLI downloads the verified Compute Node and box 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 Task's box gets a 10 GiB disk, kept until the Task is Done. - Loop-device and mount support (`fallocate`, `mkfs.ext4`, `mount`, `umount`, `findmnt`, `losetup`). - systemd as process 1. Manage it with: ```bash sudo zugoid compute status sudo zugoid compute logs sudo zugoid compute restart sudo zugoid compute disable sudo zugoid compute backup sudo zugoid compute remove --keep-source ``` `disable` and `remove --keep-source` keep Task disks. Compute backups are separate from the Server backup made by `zugoid backup`; copy them encrypted off the host. ### Update the executor The executor always runs the same release as its Server. `sudo zugoid upgrade` replaces it with the new release's Compute Node and box image once the Server is updated and healthy. You can also run `sudo zugoid compute enable --trust-shared-kernel` at any time, for example to retry. The executor keeps its pairing, its boxes and their Task disks, and settings such as `ZUGOID_NODE_BOXES`. If the replacement fails, the Server stays updated and the command to retry is printed. A Node you disabled and revoked in Settings stays off. An executor from before 0.0.10 also leaves the workspace disks of the old per-command Factory in `/opt/zugoid/factory/state/workspace-disks`. No Task uses them now. `sudo zugoid compute release-old-disks` lists them; export them with `sudo zugoid compute backup`, then delete them with `sudo zugoid compute release-old-disks --yes`. ### 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/zugoid/factory/factory.env; set +a /opt/zugoid/factory/releases/VERSION/node /opt/zugoid/factory/releases/VERSION/src/pair-local.mjs /opt/zugoid/.env /opt/zugoid/factory/releases/VERSION/node /opt/zugoid/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 the box size, how many boxes may run at once (4 by default) and the process limit, then return to Settings, confirm the authorization and choose **Activate compute node**. Each Task's box is one container with its own code repository in Artifacts. Its work is checkpointed there when a turn ends, and the box stops after 10 minutes without a turn; dependencies and build output are not kept between starts. There is no nested Docker. **Pause compute node and retain source** stops the boxes and keeps their checkpoints. See [Costs and limits](https://zugoid.com/docs/cloudflare/costs-and-limits.md) and Cloudflare's [Containers pricing](https://developers.cloudflare.com/containers/pricing/). ## How many boxes run at once A Node announces its capacity, and the Server runs as many boxes as fit. A VPS Node runs one box per CPU while each still gets 1 GiB of memory, with an equal share of CPU and memory each; set `ZUGOID_NODE_BOXES` (1 to 64) in its environment to run fewer, for a host that also does other work. A Cloudflare Node runs the number of boxes chosen at setup. A Task whose box does not fit waits and shows **Waiting for a free box**. On a VPS a box stops after 15 minutes without work; its disk stays until the Task is Done. ## 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 box 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/zugoid-workspace:VERSION . docker push YOUR_REGISTRY/zugoid-workspace:VERSION docker image inspect YOUR_REGISTRY/zugoid-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 ZUGOID_SERVER_ORIGIN=https://YOUR_SERVER export ZUGOID_NODE_STATE=/PRIVATE/COMPUTE_STATE export ZUGOID_WORKSPACE_IMAGE=YOUR_REGISTRY/zugoid-workspace@sha256:DIGEST export ZUGOID_SHARED_KERNEL_TRUSTED=1 read -r -s -p 'One-use pairing code: ' ZUGOID_PAIRING_TOKEN export ZUGOID_PAIRING_TOKEN sudo --preserve-env=ZUGOID_SERVER_ORIGIN,ZUGOID_NODE_STATE,ZUGOID_WORKSPACE_IMAGE,ZUGOID_SHARED_KERNEL_TRUSTED,ZUGOID_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 `ZUGOID_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/zugoid/blob/main/docs/software-factory.md). ## Zugoid Compute Zugoid Compute is elastic: zugoid.com starts an isolated box for each Task that needs one, pairs it with your Server, and stops it when the Task is Done. You pay by the hour of agent work, billed by the minute (each box run rounds up to the next minute, one minute at least), nothing while no Agent works, and there is no limit on how many Agents work at once. Every Server can use it, on your own Cloudflare account, on a VPS or on Zugoid Cloud. See [Pricing](https://zugoid.com/pricing). ### Link the Server In **Server settings → Compute**, the Owner chooses **Add Zugoid Compute**. The browser goes to zugoid.com, where you sign in (or create an account) and allow this Server; zugoid.com returns you to the Server, which from then on holds a Server token for your account, encrypted like its other secrets. The Compute page shows **Linked to zugoid.com account …** with **Disconnect**. There is no API key and no environment variable, and nothing is linked by an email address: only this step links a Server. A Server hosted on Zugoid Cloud is linked by itself and shows no connect step. Once linked, **Zugoid Compute** appears as a Compute Node the Server's repositories can choose, next to your VPS Node or Cloudflare Containers. Choose it when connecting a repository, or in **Server settings → Git** for an existing one. ### A box per Task When a Task on such a repository starts, the Server asks zugoid.com for a box of the Project's size and gives it a one-use pairing code bound to that Task. The box is a microVM of its own, for one Task of one Server; it pairs itself with the Server and runs the published Compute Node, so the Agent sees an ordinary Node with a durable disk. On the Compute page each running box is listed under **Zugoid Compute** as its own Node; **Release this box** stops it early. When the Task is Done or archived, the Server tells zugoid.com to stop and release the box, and billing for it ends. A box is billed while it lives, so it does not wait for the Agent's next turn. After 15 minutes without a turn the box saves its work to a private repository in Zugoid of its own and lets its machine go; billing stops there. The next message to the Agent starts a new box, which takes about a minute, and puts the work back as it was: the Agent's branch, its uncommitted changes and new files. Files the repository ignores, installed dependencies and build output are not kept, so the Agent installs or builds again. If the saved work cannot be brought back, the turn says so instead of starting over. That repository is deleted when the Task is Done. A Server that cannot hold repositories in Zugoid keeps the box until the Task is Done. A box on Zugoid Compute or a VPS Node has a screen and a browser (Chromium), so the Agent can open and play what it builds; a box in Cloudflare Containers does not have one yet. On Zugoid Compute, anyone who may read the Task can also watch that screen live while the box runs, through a link that works for half an hour; watching does not keep the box running or count as work. In the API it is `POST /api/tasks/:id/preview`, which answers `{ url, expiresAt }`, or 404 while no box runs. Set the box size per Project on the Project page, under **Zugoid Compute box size**: **Small** (the default), **Medium** or **Large**. It applies to the next box of that Project. Prices for each size are on [Pricing](https://zugoid.com/pricing). ### Credit and the spend limit You buy credit and set a monthly spend limit on your [zugoid.com account](https://zugoid.com/account). The Compute page shows the credit, what this month has spent and the limit as zugoid.com reports them, refreshed about every minute. When zugoid.com cannot be reached, the page says **zugoid.com unreachable** and keeps the last answer; boxes already running are not affected, and nothing on the Server waits for it. When a Task needs a box and the account has no credit or has reached its monthly limit, zugoid.com refuses the box and the Task shows **Needs Zugoid Compute credit** with a link to buy credit. Nothing is retried for you: buy credit or raise the limit, then start the Task again. ### Disconnect **Disconnect** releases every box of this Server on Zugoid Compute, forgets the Server token and pauses the **Zugoid Compute** Node. Repositories placed on it need another Node, or a new link, before Agents can work on them again. Linking again revives the same Node, so their choice stands. ## 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 per box, how many boxes it runs at once and its 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)] A box from Zugoid Compute comes with the published Compute Node, so it never shows this. A Node of an earlier release shows **Needs this release’s Compute Node** and gets no work until it is updated. Selecting it says what to do: for the VPS local executor, run `sudo zugoid compute enable --trust-shared-kernel`, which replaces it in place. A remote Docker Node is replaced: stop its daemon, disable and revoke it, pair this release's Node with a new code, select it in the repository environment and start new Tasks. Do the same to change a remote Node's image. --- # Servers > How an Zugoid Server owns its people and data, how you move between Servers, and how Server addresses and identity work. Source: https://zugoid.com/docs/concepts/servers A **Server** is where your organization and its data live. You use one by opening its address. ## The Server A Server is one organization 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. [Zugoid Cloud](https://zugoid.com/pricing), where we host the Server for you, is coming. 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/zugoid`: 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 use a Server directly in the browser, and a Server does not depend on zugoid.com. The address also works as a PWA. The Client is never embedded in another site. [zugoid.com](https://zugoid.com) hosts these docs and the [installer](https://zugoid.com/create). ## Several Servers The Server menu's **Add a Server** adds another Server by its address. Your browser keeps this list on that Server's address, on this device. - That Server's own page can read the list. No other Server can. - An entry is not an Account and grants no membership. You sign in to each Server separately, and your password and session stay with that Server. - Lists are not synced. Each holds only what was added there or accepted there. - Removing an entry does not delete the Server or your Account. Once a list has an entry, a rail of Servers shows at the far left. Going to another Server is an ordinary page load of another site. See [Connect to a Server](https://zugoid.com/docs/get-started/connect-a-server.md). ## Addresses A Server can answer at several public **addresses**: - The **primary address** is used for new links and background email. - **Aliases** are additional addresses that reach the same Server UUID, Accounts and content. Every listed address is trusted for login, sessions, 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 `zugoid domain` ([VPS reference](https://zugoid.com/docs/self-host/vps.md#server-addresses)). ## Related - [Accounts, Members and Roles](https://zugoid.com/docs/concepts/accounts-members-roles.md) - [Connect to a Server](https://zugoid.com/docs/get-started/connect-a-server.md) - [Custom domain on Cloudflare](https://zugoid.com/docs/cloudflare/custom-domain.md) --- # Accounts, Members and Roles > How login identity, membership and permissions work on an Zugoid Server, including the Owner, built-in Roles, Project policies and Grants. Source: https://zugoid.com/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. - **Username and password.** You sign in with your username and password. Usernames are unique on the Server, ignore case and can be changed in **My Account**. - **Email.** Email is optional. Add it in **My Account**. It counts for sign-in and password reset only once you open the link sent to it. If the Server can't send mail, it is saved but can't be confirmed yet. - **Login methods.** The Owner chooses them in **Server settings → Authentication**: username 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. - **Forgotten password.** With a verified email and working mail, use **Forgot password?** on the sign-in page. Otherwise ask the Owner, or someone who can remove members, for a [one-time reset link](https://zugoid.com/docs/get-started/invite-your-team.md#when-someone-forgets-their-password). - **Recovery.** The Owner's fallback is a recovery key. 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://zugoid.com/docs/get-started/invite-your-team.md) - [Agents, Tasks and Runs](https://zugoid.com/docs/concepts/agents-tasks-runs.md) - [Glossary](https://zugoid.com/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://zugoid.com/docs/concepts/agents-tasks-runs Agents in Zugoid 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://zugoid.com/docs/agents/mcp.md) with a credential for that Run: as itself, within its Role and no higher than the person who asked. See [External agents](https://zugoid.com/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://zugoid.com/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 | Work on a Task's box on a Compute Node. | | Propose code changes | Push to a working branch and open draft pull requests, and merge where the Project's Git flow allows it. It cannot 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 20 million tokens per Run, and 500 Runs and 200 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://zugoid.com/docs/agents/schedules.md). ## Related - [Agents overview](https://zugoid.com/docs/agents/overview.md) - [Models and providers](https://zugoid.com/docs/agents/models-and-providers.md) - [Accounts, Members and Roles](https://zugoid.com/docs/concepts/accounts-members-roles.md) --- # Files and backups > Where an Zugoid Server stores messages and files, what a Backup contains and leaves out, and why backups live apart from live storage. Source: https://zugoid.com/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 Zugoid already uses. On a VPS, `zugoid 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://zugoid.com/docs/self-host/vps.md#backups) and [Docker Compose](https://zugoid.com/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://zugoid.com/docs/concepts/releases-and-updates.md). ## Related - [Backups on Cloudflare](https://zugoid.com/docs/cloudflare/backups.md) - [Remove a Server](https://zugoid.com/docs/cloudflare/remove-a-server.md) --- # Releases and updates > How Zugoid versions and releases work, and how each kind of Server (Cloudflare, VPS, Docker, Coolify) receives an update. Source: https://zugoid.com/docs/concepts/releases-and-updates Zugoid ships as numbered releases on [GitHub](https://github.com/inchidev/zugoid/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 Zugoid: the installer at zugoid.com, 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.0.2`. - **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 changes Database 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 zugoid upgrade` on the VPS. | | Docker Compose | Set a newer image in `ZUGOID_IMAGE` and restart the stack. | | Coolify | Redeploy with `latest`, or change `ZUGOID_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 zugoid.com, 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://zugoid.com/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 zugoid 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://zugoid.com/docs/self-host/vps.md#upgrade). ### Docker Compose and Coolify Stable releases are published as `ghcr.io/inchidev/zugoid:`, `:` 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 Zugoid and shows when a newer stable release exists. See [Docker Compose](https://zugoid.com/docs/self-host/docker.md#update) and [Coolify](https://zugoid.com/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. --- # Spaces > Keep each department's work to itself on one Server, with limits on what it can use. Source: https://zugoid.com/docs/concepts/spaces A Space separates one department from the rest of your Server. People in a Space see that Space's channels, direct messages, Projects, Tasks, Mailboxes, Data, Apps, files, Agents and MCP connections, and nothing else. They do not see people from other Spaces in the Member list, in search, in mentions or as online. Accounts, Roles, the domain, backups, Providers and Compute stay with the Server. If you never create a second Space, nothing changes. Everything sits in the default Space, and the switcher appears only once there is a second one. With more than one Space, the switcher sits directly under the Server in the rail. Every section, the Inbox included, shows the Space you chose, and page titles start with its name. A dot on the switcher means another of your Spaces has an unread conversation, an unread mention or a decision waiting on you; open the switcher to see which. ## How access works Someone needs to be in the Space, and then needs the permission. A Role, a Project policy or a Grant gives nothing outside the person's Spaces, and inside them they work as usual. - **People** can belong to several Spaces. An invitation joins the Space it was created in, so someone invited to one Space sees only that Space. - **Agents** belong to exactly one Space and see only that Space. A person can use an Agent only in a Space they share with it. - **MCP tokens** reach what the Member or Agent behind them reaches. ## Who manages a Space - A **Space admin** manages that Space: its name, its members, and inside it, channels, Tasks, Data, Apps, Projects, Agents and invitations. They do not manage the Server. - The **Server Owner** can manage any Space's members, limits and deletion, but reads its contents only after joining it. Joining is recorded in the audit log. A Space that still holds conversations, Projects, Data, Apps or Agents cannot be deleted, and neither can the default Space. ## Limits The Owner can set limits on each Space, under **Server settings > Spaces**: | Limit | What happens at the limit | | --- | --- | | File storage | New uploads are refused. | | People and Agents | Nobody else can join, and no new Agent can be created. | | Agent Runs per month | New Runs are refused until next month. | | Emails per month | Sending is refused until next month. Incoming mail still arrives. | Each Space also has rate limits per person and action, such as sending messages or starting Runs. They stop runaway use and are separate from permissions. ## What a Space is not Spaces separate departments of one organization. They are not protection between organizations that do not trust each other: every Space shares one database and one set of Server secrets, and the people operating the Server can reach all of it. For separate organizations, run separate Servers. --- # Agents overview > How Agents work in an Zugoid Server, what they can do with only a model, and what code work, schedules and external tools add. Source: https://zugoid.com/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://zugoid.com/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://zugoid.com/docs/agents/models-and-providers.md) | Connect OpenRouter, Claude, ChatGPT or Workers AI and choose defaults and AI functions. | | [MCP](https://zugoid.com/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://zugoid.com/docs/agents/external-agents.md) | Add an Agent that runs on its own computer and answers through MCP. | | [Repositories](https://zugoid.com/docs/agents/repositories.md) | Let Agents take a Task to a draft pull request on GitHub. | | [Schedules](https://zugoid.com/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://zugoid.com/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://zugoid.com/docs/agents/repositories.md). | | Reminders and recurring work | **Manage schedules**. See [Schedules](https://zugoid.com/docs/agents/schedules.md). | | External read-only MCP tools, including your own MCP server | **Use external tools** for both. See [MCP](https://zugoid.com/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://zugoid.com/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), **Approve a plan, then autonomous**, or **Review every push and outbound call**, which waits for your approval of each push and secret from a Task's box and blocks other writes and outbound calls. See [Repositories](https://zugoid.com/docs/agents/repositories.md#7-run-a-task). - 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 200 million tokens. Existing Runs keep the limits they started with. An Agent merges only where its Project's Git flow allows it, after checks pass. 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://zugoid.com/docs/concepts/agents-tasks-runs.md) - [Projects and Tasks](https://zugoid.com/docs/guides/projects-and-tasks.md) --- # Models and Providers > Connect OpenRouter, Claude, ChatGPT or Workers AI to your Zugoid Server, choose a default model and assign models to AI functions. Source: https://zugoid.com/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 Zugoid recorded model capabilities may show none. Open the Provider and select **Save** once to refresh them. ### Context window and answer limit Each chosen model shows the context window and answer limit an Agent's Run will use, and where each comes from: **reported by the provider**, **set by you**, **from Pi's catalog** or **default** (128,000 and 8,192 tokens). A Run uses at most 200,000 tokens of window and 32,000 for an answer, and at most a quarter of the window for an answer. When the provider did not report a limit, for example for a private model, select **Set limits** beside it and enter the window or answer limit in tokens, then **Save**. What a provider reported is always used as reported. The model that comes with the deployment, an OpenAI-compatible endpoint set by the operator, has the same **Set limits** under its entry at the top of the list; those figures apply only while the deployment uses that model. ### Exa web search An Exa key is the other way to search. Enter it in **Server settings → Integrations → Web search**. It takes precedence over OpenRouter. ### 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 Zugoid and select **Finish connecting**. The address works once and expires in 10 minutes. When Zugoid 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 Zugoid or grant membership. Removing the connection stops its use in Zugoid and tries to revoke the sign-in. If Zugoid says remote revocation was not confirmed, disconnect Zugoid 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; Zugoid 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 under **Server settings → Integrations** takes precedence. | | Images | Draws pictures an Agent asks for while it works in a Task's box, such as the art of a game, and saves each as a file in its checkout. Needs an OpenRouter model that outputs images. Without one, Agents use the Avatars model, or else Nano Banana on the Server's OpenRouter connection. | | 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 **Zugoid style** for a toy-like black-and-white character shaped after the Zugoid 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://zugoid.com/docs/agents/overview.md) - [Create your first Agent](https://zugoid.com/docs/get-started/first-agent.md) - [Costs and limits on Cloudflare](https://zugoid.com/docs/cloudflare/costs-and-limits.md) --- # Connect an MCP client > Connect Codex, Claude or another remote MCP client to your Zugoid Server with OAuth or an access token, and use its chat, Task and Mail tools. Source: https://zugoid.com/docs/agents/mcp Every Zugoid 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 Zugoid 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 → Connections → 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) | | `data:read` | Read tables and records | | `data:write` | Create and update records | | `data:manage` | Create and configure tables | | `documents:read` | Read documents | | `documents:write` | Create and update documents | 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. 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 → Connections → 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 → Connections → 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. Every tool call passes one check before it runs: the acting Member's Role, the permissions of the person an Agent acts for, the connection's scopes and the Space of what it names. A refused call answers with the reason, such as `create_task is not available: The delegating Reader cannot perform tasks:write`. ### 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. `get_task` also returns a Task's [recording](/docs/guides/projects-and-tasks#record-a-problem) frames as images, in reading order, and the marks on the recording attachment. `list_tasks` finds the newest Tasks by Project, status or `hasRecording`. 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/zugoid/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**. There are two kinds of source, both read-only: reviewed public documentation (Cloudflare documentation and DeepWiki), and, for the Server Owner, your own HTTPS MCP server such as Coolify or DBHub. Local commands, external writes and automatic OAuth are not available. ### Connect your own MCP server 1. In **Tools**, choose **Discover / Connect** and **Custom MCP server**. 2. Enter the name, the HTTPS URL and **What it holds**, for example "Customers, subscriptions and payments". Agents read this to decide when to use it. 3. Under **Credential change**, set a bearer token, a Cloudflare Access service token or an API key. 4. Choose **Allowed Agents**, **Allowed initiating Members** and **Allowed Roles**, then save. Zugoid lists the tools the URL offers. 5. Choose the tools Agents may use, save, and select **Enable**. A URL cannot change later: to use another address, create a new connection. Zugoid refuses IP addresses, names such as `localhost` or `.internal`, and your Server's own address. Tools that say they can change data are not offered. Before you connect one, know what it shares: - **Everyone you allow sees what the credential sees.** Zugoid decides who may use a connection, not which rows or records they get. Use a read-only credential with the narrowest access you can. For DBHub, turn on its `readonly` setting and use a read-only database user. Its HTTP endpoint has no sign-in of its own, so put it behind HTTPS and Cloudflare Access or a proxy that checks a token. - **Answers appear where the question was asked.** In a channel, everyone in that channel reads the answer. To keep one to yourself, ask in your Direct Message with the Agent. - **Agents may send Server data to this service** in their requests, so connect only services you trust with it. After reading from one, an Agent cannot read web pages or search the web in that conversation, which keeps the data away from the open internet. ### Ask in chat You don't need a Task. Ask the Agent, for example "how many paid customers do we have?", and it finds the tool and answers. If you aren't allowed to use the connection, the Agent doesn't get the tool and says it can't answer. ### Connect a reviewed source 1. In **Tools**, choose **Discover / Connect** and the source. 2. Choose 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. ### Who can use a connection 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, so the Owner enables it in **Server settings → Roles** as well as granting the connection. The **Permissions** tab previews which tools an Agent would get, and **Audit** shows activity without prompts, arguments or results. ## Related - [External agents](https://zugoid.com/docs/agents/external-agents.md) - [Accounts, Members and Roles](https://zugoid.com/docs/concepts/accounts-members-roles.md) --- # External agents > Add an Zugoid 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://zugoid.com/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://zugoid.com/docs/agents/mcp.md) with the Run credential the webhook carries: as itself, within its Role and no higher than the person who asked, like any other Run. ## 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 with the credential from `server.credential` and answers with `send_message` as a reply to the message that woke it. That reply completes the Run. Other messages in the conversation are progress and keep the Run open. 4. An Agent that answers without that reply, or cannot finish, calls `complete_run`. 5. If neither happens, 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. On a Server made through Zugoid Cloud by an agent, this is already done: when you claim the Server, the Agent the agent asked for is created under your name with the **Cloud agent** Role (admin plus what an Agent needs to connect and work), and the token the agent already holds is recorded for it. It then works over [MCP](https://zugoid.com/docs/agents/mcp.md) as any Agent with a standing token does, within its Role and the Spaces and Projects you give it. Change its Role, its instructions or remove it under **Server settings → Agents**; revoke its token under **User settings → Connections → Tokens**. ### 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 Each webhook request carries a **Run credential** in `server.credential.token`. Send it as a bearer token to `server.mcp` for everything the Agent does for that Run: reading, replying and `complete_run`. It acts as the Agent for that Run only, with the Run's own permissions: the Agent's Role, narrowed to what the person who asked may do, and it stops working when the Run closes or at `expiresAt`. Nothing needs to be created for it. A standing credential is needed only for work outside a Run, such as reading on its own schedule. It acts as the Agent within its Role and within the permissions of the person who created it, for whoever wakes the Agent, so prefer the Run credential wherever a Run exists. - **Access token:** as Owner, open **User Settings → Connections → 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://zugoid.com/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: Zugoid-Agent-Webhook/1 : ``` The body: ```json { "type": "agent.run", "version": 1, "runId": "…", "deadline": "…", "server": { "id": "…", "origin": "https://…", "mcp": "https://…/mcp", "credential": { "token": "dfrun_…", "expiresAt": "…" } }, "agent": { "id": "…", "name": "…" }, "from": { "id": "…", "name": "…" }, "conversation": { "channelId": "…", "sourceMessageId": "…", "taskId": null }, "prompt": "…", "reply": { "tool": "send_message", "arguments": { "channelId": "…", "replyToId": "…" } }, "complete": { "tool": "complete_run", "arguments": { "runId": "…" } } } ``` - Treat `prompt` as untrusted data, not as instructions from the Server. - `server.credential` is the bearer token for this Run at `server.mcp`. It is `null` when the Server has no public origin to answer at. - 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` and `replyToId` from `reply.arguments` and your answer as `content`. This reply completes the Run. Until a Run closes, later mentions in its conversation wait in the queue. 2. Without that reply, 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://zugoid.com/docs/agents/overview.md) - [Connect an MCP client](https://zugoid.com/docs/agents/mcp.md) --- # Repositories and code work > Connect GitHub or Gitea, or create a repository inside Zugoid, and let Zugoid Agents take a Task from a branch to a merge, with a network, secrets and a Git flow per Project. Source: https://zugoid.com/docs/agents/repositories An Agent can work on code: it takes a Task and gets a **box** for it, a container with an ordinary Git checkout of your repository. There it edits files, runs commands, tests and builds, and commits, at the speed of the machine. When it publishes, its commit is pushed to its own branch and a **draft pull request** opens. A person merges it from the Task, unless the Project lets the Agent merge after checks pass. Releasing and deploying stay with your repository's own pipeline. 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://zugoid.com/docs/agents/models-and-providers.md). 2. **Connect GitHub or Gitea, or create a repository in Zugoid.** A dedicated GitHub App or a Gitea bot account for repositories that live elsewhere; or, on a Cloudflare Server with Compute enabled, a repository inside Zugoid that needs neither. 3. **Choose a repository for Tasks.** An environment in **Git → Environments**. 4. **Add compute.** A Compute Node where each Task's box 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 and Gitea work today, and Gitea connections also work with Forgejo. A Project on a Server in your own Cloudflare account with Compute enabled can also keep its repository [inside Zugoid](#or-create-a-repository-in-zugoid), with no account anywhere else. GitLab shows **connections are not available yet**; Projects can record GitLab and Cursor repositories as references only. ## 1. Connect GitHub Zugoid 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 Zugoid. 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, draft pull requests and merges from Zugoid, and read access to issues, checks, Actions and deployments. 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 Zugoid 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 Zugoid** stops Zugoid 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. ## Or connect Gitea Gitea has no Apps, so Zugoid uses one account's access token. Use a separate bot account: Gitea cannot limit a token to chosen repositories, so the account's own access is the limit. Only the Owner can connect Gitea, and Gitea must be served over HTTPS at its own address, not under a path. 1. In Gitea, create an account for Zugoid, for example `zugoid-bot`. 2. Add it as a collaborator with **Write** access to each repository Agents may work on. 3. Signed in as that account, open **Settings → Applications** and generate a token. Set **Repository and Organization Access** to **All**, so private repositories are included. Give it **Read and Write** for **repository** and **issue**, and **Read** for **user**: Gitea needs it to identify the account and list its repositories. Copy the token; Gitea shows it only once. 4. In Zugoid, open **Server settings → Git**, select the **Gitea** tab, then **Connect Gitea**. Enter your Gitea address, for example `https://git.example.com`, paste the token and select **Connect Gitea**. Zugoid checks the token with Gitea before saving it. The connection lists the repositories the account can push to. To change them, change the account's collaborator access in Gitea and refresh. The token stays encrypted on your Server. Agents never get it, and neither do Compute Nodes: a Node downloads and pushes the repository through your Server, with a permission bound to the Task's box. **Disable in Zugoid** stops Zugoid from using the token at once; delete the token in Gitea to revoke it there too. A personal token works too, but then Zugoid can reach every repository that person can. Gitea differs from GitHub in a few ways: - A draft pull request is a pull request whose title starts with `WIP:`. Gitea does not merge it until the prefix is removed. - **Required CI checks** match commit statuses. Gitea Actions reports each job as one. - CI job logs stay in Gitea; open the pull request there to read them. ## Or create a repository in Zugoid A Project can own its repository inside Zugoid. It lives in your Server's own storage, in the same Cloudflare account, and nobody needs a GitHub or Gitea account. This works today on a Server created on Cloudflare with Compute enabled (**Server settings → Compute → Server compute setup**), because Compute provisions the storage that holds repositories. On a VPS it is not available; on Zugoid Cloud it is coming. 1. On the Project page, under **Repositories**, select **Connect repository**, then **Create a repository in Zugoid**. With more than one computer, choose where its Tasks run. 2. Zugoid creates the repository, named after the Project, with a `main` branch and a README, and attaches it to the Project. No environment setup is needed: steps 2 and 3 below are done. A repository in Zugoid works like a connected one, with these differences: - **No pull request.** When an Agent publishes, its branch is recorded on the Task as **Published for review**. The diff is on the Run's page, where you approve pushes in review mode too. - **No CI.** Nothing tests a head before you merge it, and the Task says so. If you want checks, keep the repository on GitHub or Gitea, or build from the Zugoid repository with your own pipeline. - **A person merges.** From the Task, as for any repository, with the Project's merge method. The Agent never merges here, whatever the Git flow says, because there are no checks to pass. Zugoid merges only a branch that includes `main`'s current commit; if `main` moved, ask the Agent to bring it into its branch and publish again. - **Versions.** Under **Versions** on the Project page, someone who may merge can **Release a version**: a tag `v1.0.0` on `main`'s current commit, listed there. Zugoid records it; what a version means is up to whatever builds from the repository. - **Nothing deletes it.** Detaching the repository or archiving the Project keeps the repository and its history in your storage. To work on the repository from your own computer, ask the Server Owner: the repository is reachable with Git over HTTPS and a token from your Cloudflare account's Artifacts namespace. Zugoid does not hand out tokens yet. ## 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**, GitHub or Gitea, 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**, optionally enter exact check or commit-status names, one per line. A pull request whose required checks are missing or failing cannot be merged from Zugoid. Leave it empty to require every check CI reports. 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 on your own VPS, set up Containers in your own Cloudflare account, or **Add Zugoid Compute**, a box per Task from zugoid.com paid by the hour of agent work; **Server compute setup** shows the option that fits where your Server runs. See [Compute Nodes](https://zugoid.com/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. Open the network and add secrets A box reaches only your Server until its Project opens more. Attach the repository to a Project, then on the Project page, under **Repositories**, whoever may manage the Project sets: - **Network**: presets for the npm registry, PyPI, crates.io and GitHub (read only, no credentials), and your own hosts, such as an API your code calls. What you open here is open for every Task of the Project. - **Secrets**: tokens and keys the Agents may use, each bound to the hosts it may go to. You enter a value once; nobody reads it back, Agents included. The Agent sees a placeholder such as `ZUGOID_SECRET_TELEGRAM_TOKEN`, and Zugoid puts the value in only on requests to a bound host. When a Task needs another host, the Agent asks for a **window**: one host for up to eight hours, with the secrets it needs there. A host outside the Project's network waits for you in your Inbox. Put a token in a Project secret, never in a conversation: a conversation is history its members can read, and it is sent to the model. ## 6. Choose the Git flow The Project page also sets each Project's **Git flow**: - **Who merges**: a person (the default), the Agent once checks pass, or a direct push to the base branch for experiments, with no pull request. - **Agent branch prefix**: `zugoid-agent/` by default. Each Task's branch is this prefix and its box's id. - **Merge method**: squash (the default), merge or rebase. Your branch protection on GitHub or Gitea always has the last word. A push or merge it refuses comes back with its reason; Zugoid never works around it. A repository in Zugoid has no protection of its own: Zugoid allows a Task's branch to be pushed and nothing else, and only a person merges. ## 7. 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 in the conversation, where its answer appears as it is written, or on the **Runs** page. The Agent works in the box on the base branch's exact commit and commits as usual. **Server settings → Agents → Execution** decides what needs your approval: | Mode | Pushes | Secrets | | --- | --- | --- | | Autonomous (default) | The Agent pushes its branch on its own | Used at their bound hosts | | Approve a plan | Covered by the plan you approve once | Only those the plan names | | Review | You approve each push after reading its diff | Only through a window you open | Approvals wait in your Inbox and on the Run's page. After a push, the Agent reads the pull request's checks, CI logs and deployments for exactly that commit, and repairs on the same branch if something fails. Your existing CI, branch rules and preview deployments stay authoritative. A report from the Agent is not CI evidence. ## 8. Merge The Task shows its pull request (or, for a repository in Zugoid, **Published for review in Zugoid**) and a **Merge** button, on the board and on the Run's page. You can merge when you may approve Agent actions in the Project and every check of the pull request's head passed. If the repository reports no checks at all, you can still merge, and Zugoid says plainly that nothing tested it. For the first two minutes after a push the Task shows **Waiting for checks to register…** instead, because CI may not have reported the new head yet. Zugoid merges exactly the commit you saw, with the Project's merge method, and marks the Task's code **Merged**. The Task's own status stays yours to change. Under **the Agent once checks pass**, the Agent can merge too, but only after at least one check passed and none failed or is pending. ## Limits The Owner sets per-Task limits in **Server settings → Agents → Execution**: branch pushes (50 by default) and box runtime (six hours by default). Box size and how many boxes run at once depend on the Compute Node; see [Compute Nodes](https://zugoid.com/docs/self-host/compute-nodes.md). Git LFS is not supported yet. ## Related - [Compute Nodes](https://zugoid.com/docs/self-host/compute-nodes.md) - [Projects and Tasks](https://zugoid.com/docs/guides/projects-and-tasks.md) - [Agents, Tasks and Runs](https://zugoid.com/docs/concepts/agents-tasks-runs.md) --- # Schedules > Set one-time, daily or weekly reminders and recurring Agent work in Zugoid, and recover a Schedule that is paused or blocked. Source: https://zugoid.com/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, **Schedules** appears at the top of the **Agents** section of 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. Open **Agents** in the rail and select **Schedules**. 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/zugoid/blob/main/docs/schedules.md#api). ## Related - [Agents overview](https://zugoid.com/docs/agents/overview.md) - [Notifications](https://zugoid.com/docs/guides/notifications.md) --- # Chat > Use channels, Threads, Direct Messages, mentions, files, search and the Inbox in an Zugoid Server. Source: https://zugoid.com/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://zugoid.com/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, Zugoid 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://zugoid.com/docs/guides/calls.md). For conversations with Agents, see the [Agents overview](https://zugoid.com/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) anywhere in the Server to open **Search and commands**. Jump to a section or conversation, find Tasks, Items, databases and messages in the active Space, or create a Task or page. Search starts after two characters and shows only results you can read. Use the arrow keys and Enter to open a result. For conversation search, press ⌘⇧F (Ctrl+Shift+F), or select **Search** in the conversation header. Choose **This conversation** or **Whole server**. Results include archived channels. ## Link previews Links in sent messages can show a compact card. Links to this Server's Tasks, Items, databases, conversations and messages show their current title or excerpt only when you can read them. Website cards use the page's public title, description and image when available. Private network addresses, code blocks and hidden spoilers are not previewed. Unavailable previews leave the original link usable. An Agent's messages show cards only for links to this Server. Website cards make the Server open the address with no click, and an Agent tricked by something it read could put private data in a link. ## Inbox **Inbox** is the first rail section and the one place to catch up in the active Space. 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://zugoid.com/docs/guides/notifications.md) - [Calls and Live](https://zugoid.com/docs/guides/calls.md) - [Accounts, Members and Roles](https://zugoid.com/docs/concepts/accounts-members-roles.md) ## Following threads and message notifications Opening a Thread marks the replies you see as read. Choose **Follow** in its header to subscribe to future replies. **Inbox → My threads** lists the Threads you follow in the active Space, with unread replies first. Choose **Following** to stop following. Existing followed discussions are preserved when the Server updates. The bell in a conversation's header controls message alerts for your Account: - **Default:** mentions and followed Threads; Direct Messages also alert for messages and replies. - **All messages:** messages in the main conversation, mentions and followed Threads. - **Mentions only:** messages that mention you. - **Off:** no message alerts. Unread messages remain visible in Chat and Inbox. Thread replies inherit the parent conversation's notification setting. Reading, following and notification settings are separate; these settings grant no access to a conversation. Choose **Mark unread from here** on a message to leave it and later messages for another visit. For a Thread, this also follows it. The action returns you to Inbox; an older open tab cannot immediately mark those messages read again. Reopen the conversation to resume reading. In **Inbox → Unread chats**, the eye button previews up to five latest unread messages without marking them read. Followed Thread replies are included. Open a preview to jump to the message, or use **Mark as read** to clear a conversation and its followed Threads. ## Opening again A signed-in browser keeps what you last saw in chat, Tasks and the sidebar, so the Server opens at once and then updates. Mail, tokens and settings are never kept. Signing out, or a session that ends, removes it all from that browser. On a shared computer, sign out when you finish. --- # Projects and Tasks > Organize work on Boards with Tasks and subtasks, connect Projects, and hand Tasks to Agents. Source: https://zugoid.com/docs/guides/projects-and-tasks A **Task** records a piece of work and who is responsible for it. Tasks live on **Boards** in a Space. A Task can name a **Project** for work with repositories and Project access; without a Project it follows Server Roles. Open **Tasks** in the rail. Its list contains **My tasks**, the Tasks assigned to you in this Space and yours waiting for review, followed by Boards and **Archived**. Projects and Runs are in **Factory**. ## Boards Select a Board in the **Tasks** list. A Board collects Tasks in columns: **Backlog**, **Ready**, **Review**, **Done** and **Failed**. Members with **Manage tasks** create a Board with the **+** in the Tasks list, and rename, reorder, archive or delete it from the Board's menu. 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://zugoid.com/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. ### Subtasks Open a Task and choose **Create subtask** to break it into separately assigned work. The new Task shares its parent's Board and Project; it has its own status, priority, due date, labels, checklist, files and discussion. Creating or completing a subtask does not start an agent or change the parent's status. Use **Link existing task** to attach a top-level Task, or **Parent task → Set parent** to move a Task under another parent. **Detach** removes only the relationship. Open the parent or any child directly from the Task's hierarchy panel. Nested subtasks are supported; cycles are rejected. The progress indicator counts direct, unarchived subtasks whose status is **Done**. A failed Task is not complete. Dependencies continue to describe work that must finish first; a parent relationship does not add a dependency. Checklist items remain small checks inside one Task. Parents and subtasks must stay on the same Board and in the same Project (including no Project). Detach children before moving their parent across either boundary. Archiving a Task leaves the others unchanged and preserves the links; restore a Task before changing its parent or adding subtasks to it. Deleting a parent keeps its children and makes them top-level Tasks. Deleting an entire Board still deletes all of its Tasks. ### 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://zugoid.com/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://zugoid.com/docs/agents/schedules.md). ## Projects Select a Project in **Factory** to open its page, with its Tasks, Runs, access and repositories. The Projects page lists them all, with a search and the archived ones. 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 the **+** beside **Projects** in the list, or **Create project** on the page listing them. 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://zugoid.com/docs/agents/repositories.md). Under the repositories, whoever may manage the Project sets its **Network**, **Secrets** and **Git flow** for Agents' boxes; see [Repositories and code work](https://zugoid.com/docs/agents/repositories.md#5-open-the-network-and-add-secrets). Project access never grants Git credentials or releases, and merging from Zugoid needs Approve agent actions in the Project. 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://zugoid.com/docs/agents/mcp.md). ## Related - [Agents, Tasks and Runs](https://zugoid.com/docs/concepts/agents-tasks-runs.md) - [Chat](https://zugoid.com/docs/guides/chat.md) ## Record a problem A Task can carry a screen recording or screenshot with your marks on it. Your coding agent gets frames with your box and words on them, not a video it can't watch. Frames are cut in your browser, so the Server only stores files and text. 1. Share it from your iPhone with the Shortcut (below), or open a Task and press **Add recording**, which opens your library on a phone. 2. Mark it. On a video, scrub to the moment and press **In** and **Out**. Draw a box with the box button, or right away on a screenshot. Type what's wrong or press the microphone and say it, and add more marks with **+**. If you talked while recording, the waveform button turns what you said into clips. 3. Press **Send** (**Save** when it came from the Shortcut). With **Add recording**, nothing reaches the Server until Send. From the Shortcut, the file is already there. The Task takes its title from your first comment, its text from your marks and what you said, and its cover from a close-up of your box. Choose another image attachment as the cover with **Use as cover**. The recording follows the Task: the same access, the same Space and that Space's file storage. It stays until you delete it or the Task; there is no automatic expiry, so delete a recording that shows something it shouldn't. Voice is transcribed and dropped; only the text is kept. Other files on a Task stop at 10 MB; a recording stops at the limit the Owner sets in Server settings → Storage, 100 MB unless changed. A request through Cloudflare stops at 100 MB on most plans, and Basic storage stops at 20 MB. ### From your iPhone Share a recording straight from Photos. The file goes to your Server first, so it is a Task at once, on your **Shots** board. A page then opens where you mark it the same way and press **Save**. 1. Open your avatar → **Connections** → **Shortcut** and press **Make my link**. A link can only send recordings, to the Space you made it in, and it can't read anything. It is shown once, so copy it. **Your links** lists them, so you can turn one off. Keep it to yourself: anyone who has it can send you recordings. 2. On the iPhone in Safari, press **Get the Shortcut**, download it and open it. Tap **Set Up Shortcut** and paste your link. If it won't import, the same page has the steps to build it by hand. 3. The marking page opens in your phone's default browser. Sign in to your Server there once, or it asks you the first time, and then continues to the page. 4. In Photos, open a recording → **Share** → **Zugoid**. The first time, iOS asks to send it to your Server: tap **Always Allow**. The Shots board is an ordinary board in the Space, made the first time you need it. Choose another board while marking, and that device's recordings go there from then on. If you press **Later**, the Task stays with a **Mark** button. A file over the limit, a mistyped link or a full Space opens a page saying so, and leaves nothing behind. A Server behind Cloudflare Access can't take the Shortcut; add the recording from the Task instead. To hand it to your own agent, open your avatar → **Your agent**, connect once, and write `shots` in your repository for the newest recorded Task, `shots 4` for the last four, or `shots #12` for one. The agent looks for the Project named like the folder or with that repository connected. To let an Agent work on it here, assign it and start a Run as with any Task; the Run gets up to 8 frames. **Invite someone** on a Project page makes a link for that Project. Whoever opens it joins the Project's Space with the Coordinator policy there: they see its Tasks and can add their own. The link works 5 times, for 7 days. ## Boards belong to a Space A board belongs to a Space and holds Tasks. A Task can name a Project for agent work, whichever board it sits on, and moving it to another board keeps its Project. With no Project, a Task follows Server Roles. --- # Mail > Read, reply to, forward and triage email in shared Zugoid Mailboxes, write internal notes and control who can use each Mailbox. Source: https://zugoid.com/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://zugoid.com/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**. The Owner and the person who creates a Mailbox start with **Manage**; everyone else needs a grant. [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 Zugoid 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://zugoid.com/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://zugoid.com/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://zugoid.com/docs/cloudflare/mail.md) - [Inbox](https://zugoid.com/docs/guides/chat.md#inbox) --- # Calls and Live > Start audio and video calls, Live sessions and screen sharing in Zugoid channels and Direct Messages, and connect Cloudflare RealtimeKit as the Owner. Source: https://zugoid.com/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, Zugoid 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, Zugoid 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://zugoid.com/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://zugoid.com/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. Zugoid 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://zugoid.com/docs/cloudflare/costs-and-limits.md). ## Share a window (Portal, experimental) A Portal shows one window of your Mac to a guest, who watches in any browser without an account and can ask to control it. Unlike screen sharing in a call, only that window is sent, and what the guest may see or do is decided on your Mac: control stays off until you allow it there, and you can take it back at any time. 1. In a channel or Direct Message, select the window button (**Share a window**). 2. Select **Open PartyPortal**. The PartyPortal app on your Mac asks which window to share and connects it to the Portal. The Zugoid app for Mac will include this; until then it needs the PartyPortal app. In the Zugoid app for Mac (coming), the app first asks in its own dialog whether this Server may have you share a window, and says so if PartyPortal is not installed. 3. Copy the guest link and send it to your guest. Opening a Portal needs **Start live sessions** in your Role. The picture goes directly between your Mac and the guest whenever the network allows it; the Server only connects the two. When no direct path exists, it goes through Cloudflare's TURN relay on the Server's own Cloudflare account, which the Server sets up by itself when it keeps Cloudflare access, and which is billed to that account. The Portal ends when you select **End Portal**, when a channel manager ends it, when you lose access to the conversation, or twelve hours after you opened it; its links stop working then. ### Several viewers (preview) When the Server has Cloudflare Realtime set up, up to 16 people can watch one Portal at once. Your Mac sends the picture and sound once to Cloudflare's Realtime SFU in the Server's own Cloudflare account, and each viewer receives it from there in the size that suits their screen. For now the operator turns it on: set `REALTIME_SFU_APP_ID` and `REALTIME_SFU_APP_TOKEN`, or set `PORTAL_SFU_AUTO=1` on a Server that keeps Cloudflare access and it creates the app itself. SFU traffic is billed to that Cloudflare account. Without it, one guest can watch at a time, as before. With several viewers, nobody controls the window until you allow it. A viewer selects **Ask for control**; PartyPortal's **Share** page lists the viewers and who asks, and **Give control** hands the mouse and keyboard to one of them. **Take control back** on that page, or in PartyPortal's menu bar menu, ends it at once. **Remove** disconnects one viewer; the link keeps working for the others. ## Related - [Chat](https://zugoid.com/docs/guides/chat.md) - [Notifications](https://zugoid.com/docs/guides/notifications.md) --- # Notifications > Turn on push notifications for an Zugoid Server on each device, and learn which mentions, messages, calls, mail and reminders notify you. Source: https://zugoid.com/docs/guides/notifications Zugoid 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://zugoid.com/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://zugoid.com/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. ## Several Servers Each Server sends its own notifications, and only for its own conversations. The rail of Servers shows no unread counts for other Servers, so turn on notifications on each Server you want to hear from. On iPhone and iPad, each Server is its own Home Screen icon. ## 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://zugoid.com/docs/cloudflare/backups.md). ## Related - [Chat](https://zugoid.com/docs/guides/chat.md) - [Calls and Live](https://zugoid.com/docs/guides/calls.md) - [Schedules](https://zugoid.com/docs/agents/schedules.md) --- # Data > Keep pages, database records and canvases together, with nested pages and properties. Source: https://zugoid.com/docs/guides/data **Data** in the rail holds a Server's shared knowledge: pages, Databases and Canvases. A page, a database record and a Canvas each have one identity: an **Item**. Everything stays inside one Server and its Space. Tasks keep their own workflow. ## Databases A Database gives its records a shared set of properties. Create one with **New database**, then add fields with **+** in the header. Every record has a **Title**, and can also have formatted content and pages inside it. | Field | Holds | | --- | --- | | **Text**, **Number**, **Checkbox**, **Date** | A line of text, an amount, done or not, one day | | **Select**, **Multi-select** | One option, or any number of options, from the field's list | | **URL**, **Email**, **Phone** | An address you can open, write to or call from the record | | **Person** | Members of this Server | | **Relation** | Records of a Database, this one included; a link opens the record | - Type in the last row to add a record, or use **Record**. - Click a cell to edit it. **Open** on a row, or a card in any other view, opens its property panel. Choose **Open page** to edit the same Item's content and child pages. - **Filter**, **Sort** and **Properties** above the table change the current view as you set them. Saved views keep their own filters, sort and shown fields; **View settings** has them all in one place. - Views come as a **Table**, **Board** grouped by a Select field, **Calendar** on a Date field, **List** or **Gallery** of cards. - **Search** filters the current view. - Archive a Database you no longer use. **Archived databases** lists them for restoring. ## Pages Create a page with **New page**; its title and formatted text save as you type. **Icon & cover** changes its appearance, **Page history** shows previous text versions, and **Add page** creates a page inside it. The sidebar shows nested pages. Navigation saves your draft first; an unsaved draft is protected when you close or reload the browser tab. Use **Move** to choose a parent page or a Database. These are independent: an Item can be nested without belonging to a Database, and can belong to at most one Database. Its address, content, child pages, history, bookmarks and ordinary links stay with it when it moves. Leaving a Database or choosing another one requires confirmation to remove the old property values and incoming/outgoing Relation links. Moving a page never grants access to it: readers must have access to its parent pages and its Database too. Deleting a page moves it and its subtree out of view into **Trash**. Restore it from Data's Trash. A page whose parent or Database is still in Trash cannot be restored on its own. ## Canvases A Canvas is an Item with a board of notes and text you can arrange and connect. Add cards with **Note** or **Text**, drag them into place and link two of them with **Connect**. It also has page notes, child pages, and the same **Move** control; its board stays with it when it joins a Database. ## Who can do what Three Role permissions cover Data: | Permission | Allows | | --- | --- | | **Read data** | Read authorized Databases, Items and Canvases | | **Write data** | Create and edit pages and records | | **Manage data** | Manage Databases, fields, Canvas structure, Item locations and Trash | ## Agents and MCP An Agent whose Role and the person who asks both allow it can find tables with `list_tables`, read records with `read_records`, and edit properties with `create_record` and `update_record`. Multi-select, Person and Relation values are lists; `read_records` returns the titles of linked records alongside their IDs. `list_items`, `get_item`, `create_item` and `update_item` work with page content, including Items in Databases. `move_item` changes their parent or Database, with explicit confirmation before removing old properties. An Agent's Role can be limited to chosen Databases, pages and Canvases; nesting does not bypass those limits. MCP clients use the `data:read`, `data:write`, `data:manage`, `documents:read` and `documents:write` scopes. See [MCP](https://zugoid.com/docs/agents/mcp.md). --- # Glossary > The canonical Zugoid terms, from Server, Account and Member to Run, Server list and Backup, as they are used in the product and these docs. Source: https://zugoid.com/docs/reference/glossary These are the terms Zugoid uses in its interface, code and documentation. The source is the project's [product language](https://github.com/inchidev/zugoid/blob/main/CONTEXT.md). ## Zugoid A open-source 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 An organization 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](https://zugoid.com/docs/concepts/servers.md). ## Account A human's local login identity on one Server: a username and a password, or a linked sign-in provider. Email is optional and counts only once it is verified. 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. ## Space The boundary between departments of one Server. Each conversation, Project, Mailbox, Data resource, App, Agent and MCP connection belongs to one Space, and people see only the Spaces they are in. See [Spaces](https://zugoid.com/docs/concepts/spaces.md). ## 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://zugoid.com/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 with that Run's credential, as itself, within its Role and the permissions of the person who asked. ## 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://zugoid.com/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. Apps are paused and shown as coming soon; their stored data is unchanged. ## Project A record in one Space that holds Boards, and so Tasks, and repository references. Project policies give people and Agents access within one Project. The **Projects** section of the rail lists them with your Tasks and Runs. ## Compute Node A connected machine or provider that runs an Agent's repository workspace. See [Compute Nodes](https://zugoid.com/docs/self-host/compute-nodes.md). ## Client and Server list - **Client:** the interface served by a standalone Server at its own address. - **Server list:** the other Servers one browser keeps on one Server's address, on one device. Only that Server's own page can read it. See [Connect to a Server](https://zugoid.com/docs/get-started/connect-a-server.md). An entry in a Server list is 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 and background email. **Aliases** are additional addresses that reach the same Server UUID, Accounts and content. Every listed address is trusted, and a Server list 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://zugoid.com/docs/concepts/files-and-backups.md). ## Release A published, immutable, numbered version of Zugoid. One shared version covers the Server, installer, runtime image and Docs. See [Releases and updates](https://zugoid.com/docs/concepts/releases-and-updates.md). --- # Troubleshooting > Known Zugoid error messages and problems during install, setup, updates, Docker, mail, files and compute, with what causes them and how to fix them. Source: https://zugoid.com/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://zugoid.com/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://zugoid.com/docs/concepts/accounts-members-roles.md#account), if you saved one. ### A Member forgot their password With a verified email and working mail, they use **Forgot password?** on the sign-in page. Otherwise the Owner, or someone who can remove members, creates a one-time link in **Server settings → Members**. See [Invite your team](https://zugoid.com/docs/get-started/invite-your-team.md#when-someone-forgets-their-password). ### Signing in with an email says wrong username, email or password An email signs in only once it is verified. Sign in with your username, then check **My Account → Email**. If the Server can't send mail, the email can't be confirmed yet. ### 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 (`ZUGOID_ORIGINS`, `zugoid 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 ZUGOID_CHANNEL=prerelease." | Add `ZUGOID_CHANNEL=prerelease`, or use a stable release. | | "An existing Server has another version; use zugoid upgrade ." | Rerunning an installer only restarts the same version. Use `sudo zugoid 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 `zugoid` command to finish. | | "ZUGOID_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 zugoid upgrade and zugoid backup." | Remove `ZUGOID_AUTOUPDATE` and `ZUGOID_BACKUP_SCHEDULE`. | | "Choose another primary before removing this address." | Run `zugoid 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 zugoid 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 Zugoid X; image Y is older" Migrations only go forward. Set an image at least as new as the version in the message (`ZUGOID_IMAGE` in Docker Compose, `ZUGOID_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 `zugoid` container exits at start The entrypoint checks its configuration first: | Message | Fix | | --- | --- | | "zugoid: set AUTH_SECRET" (or another name) | Set the variable in `.env`. | | "zugoid: 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. | | "zugoid: CLOUDFLARE_ACCOUNT_ID must be 32 hex characters" | Copy the account ID from the Cloudflare dashboard, or leave it empty. | | "zugoid: ADMIN_USERNAME must be 3 to 32 letters, digits, dots, hyphens or underscores when ADMIN_PASSWORD is set" | Fix `ADMIN_USERNAME`. | | "zugoid: 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 Zugoid" That is expected on a Docker or Coolify Server. Update by changing the image tag. See [Docker Compose](https://zugoid.com/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 zugoid.com**). 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. ## 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 Zugoid 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/zugoid/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://zugoid.com/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://zugoid.com/docs/self-host/compute-nodes.md). ### Compute Node setup fails on a VPS | Message | Fix | | --- | --- | | "Compute Node needs 17 GiB free disk capacity for its box image, a 10 GiB Task disk when it is new and reserve for the Server." | Free disk space or use a larger disk. Replacing an installed Node asks for 8 GiB. | | "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 zugoid 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 local Compute Node was not replaced and gets no work until it is." | Fix the reason printed above it, then run `sudo zugoid compute enable --trust-shared-kernel`. | | "This local Compute Node was disabled and revoked in Server settings → Compute, so it stays off." | The Owner revoked it. It is not paired again on its own. | If only the compute step fails during a new install, the Server stays installed and the script prints the command to retry. ## Server list ### A Server is missing from the rail Each list is kept by one browser, on one Server's address, on one device. Another device, another browser or another of your Servers has its own list, and clearing a site's data clears its list. Add the Server again with **Add a Server** in the Server menu. ### Add a Server refuses an address The address must be one the other Server lists as its own, and that Server must be up. If it says **This address is not a compatible Zugoid Server**, check the address, then update the older of the two Servers.