About ten minutes, Docker, and a mailbox you can connect. Nothing is sent to a server we own, and there is no account to make with us.
This product runs on your machine. The database is on your machine. Your mail stays on your machine. Nothing is sent to a server we own, and there is no account to make with us.
There are two ways to install it. Install it on this computer. That is the install every feature works on: the first plan, the terminal beside each task, and the agent that runs on your own Claude Code login. Docker is for a server with no screen, and on it the desk cannot open a terminal window, so the first plan is not drawn there.
You need about ten minutes, and these on a Linux computer with a desktop:
| You need | Why |
|---|---|
| Docker, and it must be running | It holds the database, and only the database |
Node 22 (nvm install 22) | It runs the desk |
Claude Code, signed in once (claude) | The agent that draws your plan and does the work |
tmux 3.4 or later and Konsole (sudo apt install tmux konsole) | The first plan and each task's terminal open in a Konsole window, in tmux |
A mailbox is not needed to start. Setup lets you connect one later.
Get the files. Download them, check them and unpack them in your home folder:
cd ~
curl -fLO https://konvenient.ai/download/konvenient-latest.tar.gz
curl -fLO https://konvenient.ai/download/konvenient-latest.tar.gz.sha256
sha256sum -c konvenient-latest.tar.gz.sha256
tar xzf konvenient-latest.tar.gz
cd konvenient-*/
sha256sum must print konvenient-latest.tar.gz: OK. If it prints anything else, the download is damaged: delete both files and download them again.
Run the install script:
bash agentic-os/deploy/install.sh
It starts the database, writes agentic-os/brain/.env with a new encryption key, installs the code's packages, applies the database changes, and starts the desk as a service of your login. Keep a copy of the ENCRYPTION_KEY line of agentic-os/brain/.env off this computer. Without it no stored password or key can be read again.
Open the address the script prints last, http://127.0.0.1:8787/app/. The setup page walks the rest: step 5 asks for the folder the agent works in.
On the empty desk, write what you want finished in one sentence. A Konsole window opens, and you and the agent draw the first plan in it. Say yes, and the plan is on your desk and the agent starts its first piece.
The desk is on this computer and on no network. The script starts no public tunnel. To reach the desk from a phone, read Reaching the desk from a phone.
Five settings change what the script installs, and each has a default that needs no change: KONVENIENT_PORT (8787), KONVENIENT_PG_PORT (5434), KONVENIENT_PREFIX (agentic, the start of every service and container name), KONVENIENT_TUNNEL (1 adds a public tunnel) and KONVENIENT_SKIP_PLUGIN (1 leaves Claude Code's plugins alone). For example, a second install beside the first:
KONVENIENT_PREFIX=second KONVENIENT_PORT=8830 KONVENIENT_PG_PORT=5454 bash agentic-os/deploy/install.sh
Dry run is on. It holds every mail and message to a customer until you turn it off on the settings page. A run you start yourself — Run with Claude, or the first piece of a plan you said yes to — is not held.
Docker runs the desk and its database as two containers. You need about ten minutes, and Docker, running. Node 22 on the host runs the agent helper, so that your agent works on your own login. See Starting the agent helper.
On Docker the desk cannot open a terminal window. The first plan and a task's terminal need the install on this computer. Run with Claude works on both.
Optional on day one:
Your own Google or Microsoft application, if you do not want to use an app password. See Connecting a mailbox.
poppler-utils, the package that reads the text of a PDF. The Docker image carries it, so a Docker install needs nothing. Add it only where the desk runs on the machine itself. See Reading the text of a PDF.
Install Docker Engine and the compose plugin from your distribution.
Get the files as step 1 of Install on this computer says, and go to the agentic-os directory:
cd agentic-os
cp deploy/customer/.env.template .envOpen .env and fill in the three values it marks FILL THIS IN: POSTGRES_PASSWORD, ENCRYPTION_KEY and OPERATOR_EMAIL. The file says how to generate the first two. Set DEFAULT_TENANT to your company's short name. ANTHROPIC_API_KEY is needed only if you have no Claude subscription.
Set AGENT_BRIDGE_BIND=172.17.0.1 in the same file. That is the docker gateway address. Read Starting the agent helper before you do: on Linux this address is not only yours.
Start it:
docker compose up -d
docker compose logs brainThe last line of the log is the address of your desk. Open it in a browser. Nothing opens a browser for you.
Install Docker Desktop, and start it. The whale icon in the menu bar must be there.
Do steps 2 and 3 of Docker on Linux.
Leave AGENT_BRIDGE_BIND=127.0.0.1. Docker Desktop sends the container to your loopback address, so the helper stays yours alone.
Run docker compose up -d, then docker compose logs brain.
Install Docker Desktop with the WSL 2 backend, and start it.
Do the same steps in a WSL 2 shell, or in PowerShell in the agentic-os directory.
Leave AGENT_BRIDGE_BIND=127.0.0.1.
Run docker compose up -d, then docker compose logs brain.
Tested platforms. We test on Linux. macOS and Windows use the same two containers and the same commands, and we have not run them end to end. Tell us what breaks.
The first time you open the desk, it sends you to the setup page. The page has five steps and one Done step. It runs no JavaScript, so each step is a form you submit, and each step says what you need before you start it.
The page closes for ever when you press Open my desk. After that, everything on it lives on the settings page instead.
Step 1 is four short lists: what this software does, what it does not do, what we collect, and what warranty you get. Tick the box and press continue.
The tick is checked on the server. Nothing is created before it arrives — no account, no row, no key. If you post the form without the tick, the page says: Tick the box to continue. Nothing is created until you do.
Step 2 makes you the operator of this install. Every action the desk takes carries this address.
Step 2 answers with your key. It is 32 random characters, and it starts with op_. This is how you sign in.
Copy it now. The server stores a salted hash of the key and not the key, so nobody — including us — can show it to you again. If you lose it before you finish setup, come back to step 2, type the same email, and press the button again. The old key stops working and you get a new one.
If you lose it after setup is finished, read I lost my key.
If two people open the setup page on the same machine, the first one to finish step 2 becomes the operator. The second reads: Somebody already finished setting up on this machine. Sign in instead. The database decides this, not a lock, so there is no race you can lose twice.
Step 3 asks for your timezone. The page cannot read one from your browser, because it runs no script, so it offers the container's own timezone. That is UTC unless you set TIMEZONE in .env. Pick your own from the list.
What you pick here decides every clock on the product, not only this page: the time on the top bar, the hour beside every mail, which tasks count as due today, and the hour the daily digest goes out. You can change it later on the settings page, and every one of those moves with it.
There is no language field. The desk is in English. It used to ask, store your answer and change nothing (F-127, F-128); the question comes back when a second language does.
Step 4 asks for one mailbox. There are three paths that work and one that does not yet:
| Path | Use it when | What you need first |
|---|---|---|
| Any mailbox over IMAP | Always. This is the path that certainly works | An app password |
| Google Workspace | You have an organisation, and you want the consent screen to show its name | Your own Google application |
| Microsoft 365 | The same, in your own Azure tenant | Your own Azure application, and an administrator |
| Outlook.com personal | Not yet | Use IMAP instead |
Start with IMAP. It needs no application registration, no administrator and no review. You can connect a second mailbox later.
The connect must finish in ten minutes. The Google and Microsoft paths send your browser to the provider and back. The page holds the connection in one sealed cookie for ten minutes. If you take longer, the page says This connection took too long — start it again, and nothing is connected. Press the button again.
If you press Cancel on the provider's screen, the page says Nothing was connected. You can start again. Nothing is half-connected. The desk writes the mailbox only after the whole round trip succeeds.
An app password is a password your mail provider makes for one program. It is not the password you type into their website. Most providers refuse the website password over IMAP, and the refusal does not say why.
| Provider | Where you make it | Read this first |
|---|---|---|
| Gmail | myaccount.google.com → Security → App passwords | Gmail over IMAP |
| iCloud | account.apple.com → Sign-In and Security → App-Specific Passwords | Apple calls it an app-specific password |
| Fastmail | Settings → Privacy & Security → Integrations → New app password | Fastmail |
Paste the app password into the setup page. Remove the spaces if the provider shows it in groups of four.
Turn on 2-Step Verification first. Google issues no app password until you do, and the App passwords page is not in the menu before then.
Go to myaccount.google.com → Security.
Turn on 2-Step Verification.
Open App passwords, name it, and copy the 16 characters.
On the setup page use imap.gmail.com, port 993, your full address, and that password.
If you use the ordinary password, Gmail answers Invalid credentials (Failure). That is the same message it gives for a wrong password, so the message cannot tell you which of the two is wrong. Make the app password.
The Basic plan does not allow IMAP or SMTP at all. No app password works on it. The server refuses the login and never says the plan is the reason. If you are on Basic, you must change the plan or use another mailbox.
On any other plan, make an app password with the Mail access level, and use imap.fastmail.com on port 993.
| Provider | IMAP | SMTP |
|---|---|---|
| Gmail | imap.gmail.com, port 993 | smtp.gmail.com, port 465 |
| iCloud | imap.mail.me.com, port 993 | smtp.mail.me.com, port 587 |
| Fastmail | imap.fastmail.com, port 993 | smtp.fastmail.com, port 465 |
| Your own server | ask your administrator | ask your administrator |
Port 993 is the encrypted one. If you type 143, the handshake fails and the page says the port may not be the encrypted one. Use 993.
If the page says nothing answered the host and the port, compare both against the table above. The page prints exactly what it tried, character for character.
The desk gives an unknown host five seconds to answer. It never waits longer, so a typed character that is wrong costs you five seconds and not a minute.
Do this only if you want the Google Workspace path. You do not need it to start.
Open console.cloud.google.com with an account in your Workspace organisation.
Make a project, or pick one.
Open APIs & Services → OAuth consent screen. Set the user type to Internal. Read Google Internal vs External first.
Enable the Gmail API for the project.
Open Credentials → Create credentials → OAuth client ID. The type is Web application.
Under Authorised redirect URIs, paste this address exactly:
http://localhost:8787/app/setup/callback/gmail
Use the address you open the desk on. If you reach the desk through a tunnel, add that address too, with the same path: https://<your host>/app/setup/callback/gmail. Google compares the whole string, including the scheme and the port.
Copy the client ID and the client secret into step 4 of the setup page.
If Google answers redirect_uri_mismatch, the page prints the exact address this machine sent. Paste that address into the registration. It is the most common first failure, and it is always one character.
If Google answers about the client id or the secret, check which of the two you pasted into which field.
Set the user type to Internal. An External app in Testing expires its connection after seven days, and then the desk stops reading your mail with no warning. An Internal app does not expire that way.
The consent screen shows a warning when the app is External and unverified. If you see that warning, stop and change the user type.
Internal needs a Google Cloud organisation. A personal @gmail.com account has none, so it cannot register an Internal app at all. Connect a personal Gmail over IMAP with an app password. See Gmail over IMAP.
This is the one thing we cannot tell you for certain.
The desk reads mail with the gmail.readonly scope. Google calls that a restricted scope. Google's own verification page says an application that uses a sensitive or a restricted scope must complete verification, and that page names no exemption for an Internal app. A different Google page says an internal-only app is not subject to the unverified-app screen and not subject to the 100-user cap.
The two pages do not meet. Whether your Internal app can use gmail.readonly without a Google review is NOT CONFIRMED. We read three of Google's own pages on 2026-09-08 and none of them answers it. We hold no Workspace organisation, so we cannot test it either.
What this means for you. Try the Google path if you want it. If Google refuses the scope, the page prints Google's own words. Then connect the same mailbox over IMAP with an app password. IMAP asks Google for no scope, needs no review, and certainly works. That is why the setup page puts IMAP first.
Do this only if you want the Microsoft 365 path.
Open portal.azure.com → Microsoft Entra ID → App registrations → New registration.
Give it a name. For Supported account types, choose Accounts in this organizational directory only.
Under Redirect URI, choose Web and paste this address exactly:
http://localhost:8787/app/setup/callback/outlook
Add your tunnel address too if you use one: https://<your host>/app/setup/callback/outlook.
Open Certificates & secrets → New client secret. Copy the Value, not the secret ID. Azure shows the value once.
Open API permissions, and add these delegated Microsoft Graph permissions: offline_access, User.Read, Mail.Read and Mail.Send.
Copy the Application (client) ID, the Directory (tenant) ID and the secret into step 4 of the setup page.
If Microsoft answers AADSTS50011, the redirect address does not match. The page prints the exact address this machine sent. Paste that.
An administrator of your Microsoft tenant approves the application once. Until they do, Microsoft answers AADSTS65001 or AADSTS90094 and the connection stops.
The administrator opens App registrations → your app → API permissions and presses Grant admin consent. You are the administrator on many small tenants. Ask your IT department on the rest.
One permission covers both a new mail and a threaded reply: Mail.Send. There is no separate reply permission.
If you connected the mailbox before Mail.Send was on the registration, the desk reads that mailbox and refuses to reply from it. The desk says: This mailbox is connected for reading only. Reconnect it to let the agent reply. Add the permission, grant consent again, and connect the mailbox again.
A personal @outlook.com, @hotmail.com or @live.com account cannot be used with an application registered for one organisation. That account type needs a different registration, and we do not ship one yet.
Connect a personal Outlook.com mailbox over IMAP with an app password instead. Nothing is half-connected when you press that choice: no row is written.
The desk makes judgments with an agent. The agent runs on your machine, on your login. Step 5 asks which one, and how you pay for it.
| Choice | Install it with | What it costs |
|---|---|---|
| Claude Code, on your subscription | npm install -g @anthropic-ai/claude-code, then claude once to sign in. Then add the plugin: claude plugin marketplace add <your checkout of this repository>, claude plugin install agentic-os@agentic-os --scope user, and mkdir -p ~/.config/agentic-os && (umask 077 && echo 'AGENTIC_OS_URL=http://127.0.0.1:8787' > ~/.config/agentic-os/terminal.env). That file has mode 0600 and holds two lines: AGENTIC_OS_URL, which the command above writes, and AGENTIC_OS_TOKEN=<the key the setup page showed you>, which you add yourself | Nothing more than the subscription |
| Claude, on an API key | Nothing to install | About $0.017 per mail, measured |
| Codex | Install the Codex CLI, then sign in once | Your own OpenAI plan |
Install the agent on the host, not in the container. The container cannot see your subscription, and it must not hold your key.
agentic-os/deploy/install.sh runs those three plugin commands for you when claude is on PATH. When it is not, the script prints the three commands and goes on. The install never holds your key. The desk keeps only a hash of it, so the setup page shows the key once, and you add the AGENTIC_OS_TOKEN line to the file yourself.
After an update of this repository, refresh the plugin, or the terminals keep the old skills: claude plugin marketplace update agentic-os, then claude plugin update agentic-os@agentic-os --scope user. A new install and a second run of install.sh do this for you.
If the setup page says the command is not on this machine, install it and press the button again. If the command is there and refuses to run, the page prints the program's own error. Read that line first: it is usually a sign-in that has not happened yet.
If the setup page says a directory does not exist, the host could not find it. The helper checks the path, because the container cannot see your disks. Give a path that exists on the machine where the helper runs.
The desk runs in a container. Your agent runs on your login. One small helper joins the two. It is a single Node file, it imports nothing but Node itself, and you start it yourself:
node deploy/customer/agent-bridge.mjs ~/work
Every directory you name is a directory the agent may work in. It may work in no other.
On the first start the helper prints a secret, once. Paste it into AGENT_BRIDGE_SECRET in agentic-os/.env, then run docker compose up -d again.
If step 5 says the helper is not running, start it. If step 5 says the helper is running and refused this machine, the secret in .env is not the secret the helper printed. Copy it again.
The honest limit, and you should read it before a shared machine. The helper listens on AGENT_BRIDGE_BIND. On Docker Desktop that is 127.0.0.1, which only you can reach. On Linux it must be the docker gateway address, because a container cannot reach the host's loopback — so any container on docker's default network can reach it, not only a local user. The program allow-list holds it to two names, claude and codex. The directory allow-list holds it to the directories you named. The secret is compared in constant time. Those three bound the damage; they do not remove it. On a single-user laptop this is the smallest surface that does the job. On a shared machine this is not the smallest surface, and you should not run the helper there.
You may choose Codex, and the desk labels it an experiment wherever it appears. The judgment prompts were written and measured on Claude. Codex answers them, and we have not measured how well. Choose Claude if you want the behaviour this product was tested with.
Choose the API path only if you have no Claude subscription. Get a key at console.anthropic.com, and paste it into step 5. It is stored encrypted with your ENCRYPTION_KEY, and it never leaves your machine except in a call to Anthropic.
If Anthropic refuses the key, the page prints Anthropic's own message. A key that was made for a different organisation is the usual reason.
The API path asks for a monthly limit in dollars. There is no default, and there is no limit until you type one. Type a number you are willing to lose.
The desk counts what it spends in your own timezone's month. When the month's spend reaches your limit, the desk stops calling the model and says so on every page. Nothing is deleted and nothing is lost — the queue waits for the next month, or for a higher limit that you set on the settings page.
Read this paragraph before you press Run with Claude the first time.
A run launches the agent on your own machine with every permission granted. The desk starts Claude Code with --permission-mode bypassPermissions, which means the agent does not stop to ask before it reads a file, writes a file, deletes a file or runs a command. It acts as your login acts, so it can reach every file and every program your login can reach, and nothing outside it. It runs only when you press the button, only on the task you pressed it on, and it stops when the task ends. Nothing on your machine is sent anywhere except the words the agent sends to the model you chose. If that is more than you want to give it, run the desk on a machine that holds only the work you want it to touch, or under a login of its own — and do not press Run.
The desk still refuses, on its own, the four things a run may never do: it moves no money, it sends nothing outbound over the daily cap, it acts on nothing below the confidence floor, and it never approves its own work. Those refusals hold whatever the agent asks for. The permission the paragraph above is about is the permission to touch the files and the programs on this machine, and it is a whole one.
An empty desk asks "What do you want finished?". You write one sentence. The desk then opens Claude Code in a terminal window on your screen, and you and the agent draw your first plan in that window.
The desk can open that window only when it runs on this computer, not in Docker. A container has no terminal program, no tmux, no Claude Code and no screen. On a Docker install the desk starts nothing, and it shows this line: "The plan conversation needs the install on this computer, not Docker: run bash agentic-os/deploy/install.sh".
To draw the first plan, use the host install. On Linux, run bash agentic-os/deploy/install.sh. The desk checks the items below before it opens the window. When an item is missing, the desk starts nothing and shows the line in the right column.
| This computer needs | The line the desk shows when it is missing |
|---|---|
A folder for the agent (CLAUDE_REPOS) | "Name the folder the agent works in first" |
| Claude Code (see the table above). Sign in to it once before the first plan | "Claude Code is not installed" |
| Konsole | "Konsole is not installed" |
| tmux 3.4 or later | "tmux is not installed" |
| A desktop session with a screen | "This computer has no screen for the terminal" |
The stack publishes port 8787 on 127.0.0.1 and nowhere else. That is the promise of this install: the desk is on your machine and on no network.
To reach it from a phone, put a tunnel in front of port 8787. Then set REMOTE_URL in agentic-os/.env to the address the tunnel gives you, and start the stack again.
Two paths are below. Both are free, and both take about ten minutes:
Tailscale — the fastest one. Your phone and your machine join one private network. Nothing is published to the internet.
Cloudflare Tunnel — a name on a domain you own, on the public internet. Pick this one if other people must reach the desk.
A session belongs to the hostname it was made on. The desk's session cookie names no domain, so a browser sends it back to one hostname and to no other. A tunnel that hands out a new hostname on every start therefore signs your phone out on every restart, and every bookmark you kept points at an address that no longer answers.
So use a fixed name. Both paths below give you one. The cloudflared tunnel --url command does not — it makes a throwaway name of four random words, and it is the one thing to avoid here.
The Done step tests the address from inside the container, and the container is not your phone. If the last step of the setup page says
This machine could not reach the desk at
https://…— a phone still may.
then the test to trust is the one you run on the phone:
Take the phone off Wi-Fi, or leave it on — either is a valid test, and both is better.
Open the address in the phone's browser.
You must see the sign-in form. Paste your key. The desk opens.
A failed probe is not proof that the tunnel is broken. The container asks public DNS from inside a docker network, and that network does not always hairpin — a name that points back at the same machine often does not resolve back to it from inside a container. Tailscale's ts.net name is the plain case: nothing in the container has joined your tailnet, so the container can never reach it and your phone always can. The address on this machine keeps working either way.
If the address answers with a different version of this software, you are looking at another install. Check what the tunnel points at.
Free on the Personal plan, for up to 3 users and 100 devices. Your phone and your machine join one private network. Nothing is published to the internet, and no domain is needed.
The cost of that: every device that opens the desk must run the Tailscale client and be signed in to your account. Pick the Cloudflare path instead if somebody must reach the desk from a device you do not control.
Make a Tailscale account, and install Tailscale on the machine the desk runs on:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
On macOS and Windows, install the app and sign in.
In the Tailscale admin console, open DNS. Turn on MagicDNS, then turn on HTTPS certificates. Both are free, and the second one is what gives you a name your phone's browser trusts.
Put Tailscale in front of port 8787, on the machine the desk runs on:
sudo tailscale serve --bg 8787
tailscale serve status
serve status prints the name, and it reads like https://your-machine.tailnet-name.ts.net. That name does not change, on this machine or on any restart.
Put the name in agentic-os/.env, then start the stack again:
REMOTE_URL=https://your-machine.tailnet-name.ts.net
docker compose restart brainInstall the Tailscale app on the phone. Sign in with the same account. Open the name from step 3 in the phone's browser, and sign in with your key.
Expect the setup page's last step to say the desk could not be reached at that address. That is correct here, and it is not a fault: the container has not joined your tailnet. The phone is the test.
Free, and it needs one thing Tailscale does not: a domain name you own, with its DNS on Cloudflare. The Cloudflare plan is the free one. The result is a name like desk.example.com that does not change when anything restarts.
Add your domain to Cloudflare, and point the registrar at Cloudflare's name servers. Cloudflare's own setup walks you through it.
Install cloudflared on the machine the desk runs on, and sign it in. The command opens a browser, where you pick the domain:
cloudflared tunnel loginMake a named tunnel. The name and its credentials file are kept, so the tunnel is the same tunnel after every restart:
cloudflared tunnel create desk
cloudflared tunnel route dns desk desk.example.comTell it what to serve. Write ~/.cloudflared/config.yml:
tunnel: desk
credentials-file: /home/you/.cloudflared/<the id create printed>.json
ingress:
- hostname: desk.example.com
service: http://127.0.0.1:8787
- service: http_status:404Run it, and keep it running:
sudo cloudflared service install
To try it first, run cloudflared tunnel run desk in a terminal instead.
Put the name in agentic-os/.env, then start the stack again:
REMOTE_URL=https://desk.example.com
docker compose restart brainOpen https://desk.example.com on the phone, and sign in with your key.
Do not use cloudflared tunnel --url. That is the quick tunnel. It needs no account and no domain, and it hands out a new four-word hostname every time it starts — which signs every phone out on every restart.
Your desk is on the public internet now, and your key is the only thing in front of it. Two things to do on the same day: keep the key in a password manager, and put Cloudflare Access in front of the hostname if other people use the machine. Access is free for up to 50 users. It is a second door, not a replacement for the key.
The desk fills as the mail sync runs. The first pass reaches back 30 days and takes at most 200 messages. Set MAIL_SYNC_ENABLED=1 in .env and restart to turn the sync on.
DRY_RUN=1 is the default and it should stay on for a while. Every action the desk decides on lands in the Outbox instead of being carried out. Nothing is sent. Read the proposals for a few days first. Turning it off is a hand edit you make on purpose.
The desk asks a person whenever it is not confident enough, or when money is above the cap. Those two numbers are CONFIDENCE_FLOOR and AMOUNT_CAP in .env. Tighten them. Do not loosen them on day one.
A judgment on the subscription path takes about a minute. On the API path it takes a few seconds and costs about $0.017.
You can attach documents to a project. The desk reads the text of a PDF with pdftotext, from the operating system package poppler-utils. It is the only package the documents feature adds, and it costs nothing.
On the Docker install you need to do nothing. The brain image carries poppler-utils, so the desk reads PDF text as soon as the container starts. Do not install the package on the host: the desk does not run there.
Where the desk runs on the machine itself, the package is optional. Without it you lose one thing: the desk does not read the text of a PDF. The upload still works, and the file is still stored and listed. The agent gets the document's name and its summary only, never what the PDF says. Every other part of the desk works the same.
To see which case your machine is in, open Settings. One line says whether the desk can read PDF text on this machine. When it cannot, the line names poppler-utils. The start-up log names the missing package too.
To add the package where the desk runs:
| Where the desk runs | Command |
|---|---|
| Debian or Ubuntu | sudo apt install poppler-utils |
| Fedora | sudo dnf install poppler-utils |
| macOS with Homebrew | brew install poppler |
These commands reach a desk that runs on the machine itself. The Docker install needs none of them: the desk runs inside the brain container, and the image of this release installs poppler-utils. A package you install on the host never reaches the container, which is why the image carries it.
Restart the desk after you install it. The desk looks for pdftotext once, when it starts. If the program is not on the PATH, set PDFTOTEXT_BIN in .env to its full path.
A Word file is stored and listed, but the desk does not read its text, with or without this package.
You see Docker's own line:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
Is the docker daemon running?
Start Docker Desktop, or sudo systemctl start docker on Linux. Then run docker compose up -d again.
The command stops. It does not wait and it does not poll, so you never sit in front of a command that will never finish.
You see:
Bind for 127.0.0.1:8787 failed: port is already allocated
Something else on this machine holds port 8787. Open agentic-os/compose.yml and change the left half of the ports line:
ports:
- '127.0.0.1:8888:8787'
Set PORT in .env to the same number is not needed: the right half is the port inside the container, and it stays 8787. Run docker compose up -d again and open the new address.
If you registered a Google or Microsoft application, the redirect address changed with the port. Update it in the registration too.
Two different faults show here, and they read differently.
A migration failed on first boot. The brain prints the migration runner's own error and the name of the file that failed, then stops. Read the file name. Send us the log: we cannot guess this one.
docker compose logs brain
Every page answers "Internal Server Error". The image was built or started without CUSTOMER_INSTALL=1, so nothing built the schema. Check that line in agentic-os/.env, then:
docker compose up -d --force-recreate
The migration runner is safe to run again. It applies only the files that are missing, it takes a lock, and it checks what it already applied.
The key is stored as a salted hash, so nobody can read it back to you. If you are still signed in on any device, make a new key on the settings page. That is the short way.
If you are signed in nowhere, make a new key on the computer the desk runs on.
Installed on this computer. Run this in the agentic-os/brain directory, with your own mail address:
npm run key -- you@example.com
It prints your new key. Paste it on the sign-in page.
Installed with Docker. Run this in the agentic-os directory, with your own email address in the last line:
KEY="op_$(openssl rand -hex 16)"
SALT="$(openssl rand -hex 16)"
HASH="$(printf '%s%s' "$SALT" "$KEY" | openssl dgst -sha256 | awk '{print $NF}')"
docker compose exec -T db psql -U agentic -d agentic -c \
"update app.people set token_hash='$HASH', token_salt='$SALT' where email='you@example.com';"
echo "Your new key: $KEY"
Copy the key it prints, and sign in with it. The old key stops working. Your identity, your history and your mailboxes are untouched — this changes one row's two columns.
If the update answers UPDATE 0, the email address does not match the row. List them:
docker compose exec -T db psql -U agentic -d agentic -c "select email from app.people;"
/health answers the version, and every page shows it in the footer. Both read one string: APP_VERSION, baked into the image when it is built.
Who bumps it, and when. The maintainer who cuts a release bumps APP_VERSION in agentic-os/deploy/customer/.env.template and builds the image with the same value:
docker compose build --build-arg APP_VERSION=0.2.0
The version changes with every release. Nothing in the software enforces that; a person does it. If two installs report the same version and behave differently, one of them was built without the argument, and it fell back to the version in package.json.
One command updates both installs. It checks the release's signature and your key, takes a backup, and only then installs the new release:
| Install | Run this |
|---|---|
| On this computer | bash agentic-os/deploy/customer/update.sh |
| Docker | in agentic-os: bash deploy/customer/update.sh --mode tarball |
The backup comes first. A new release changes the database when it starts. So the command takes a backup before that, reads it back, and prints its file name. If the backup fails, the update stops and nothing changes: the release you have keeps running. On this computer the backup goes to ~/backups/agentic-os/, with the passphrase in ~/.config/agentic/backup.env. On Docker it goes to the agentic-backups volume.
On this computer the command stops the desk, puts the new files in place, applies the database changes, and starts the desk. On Docker it rebuilds the stack, and the container applies the database changes on start, because CUSTOMER_INSTALL=1.
If you get the new files yourself instead, take the backup yourself first. On this computer, run bash agentic-os/deploy/install.sh again: when the database already holds your data, it takes the backup and stops if the backup fails. On Docker:
docker compose run --rm -T --entrypoint bash backup /opt/agentic/backup.sh \
&& docker compose build && docker compose up -d
Your data stays in the named volume. Never run docker compose down -v: the -v deletes that volume, and with it your database.
If the new release does not work, put the previous one back:
| Install | Run this |
|---|---|
| On this computer | bash agentic-os/deploy/customer/update.sh --back |
| Docker | in agentic-os: bash deploy/customer/update.sh --back |
The update keeps the files of the release it replaced in agentic-os/.releases/. The command puts those files back, sets APP_VERSION to that release, and restarts. The footer then shows the previous version. --back needs no key and no network.
Going back does not touch the database. Every task, mail and setting stays as it is, including what you did after the update. This is safe because a database change that a release applies by itself only adds tables and columns (constitution § V, 1.2.0). The previous release reads the newer database and ignores what it does not know.
When a database change itself failed. Each database change runs whole or not at all, so a failed one leaves nothing half done, and --back is still the first step. If the desk still fails after --back, restore the backup the update took. Its name is in the update's output and in agentic-os/.releases/pre-update-backup. A restore replaces the whole database with that backup, so work done after the update is lost.
On this computer:
systemctl --user stop agentic-brain.service
set -a; . ~/.config/agentic/backup.env; set +a
openssl enc -d -aes-256-cbc -pbkdf2 -pass env:BACKUP_PASSPHRASE \
-in "$(cat agentic-os/.releases/pre-update-backup)" \
| docker exec -i agentic-postgres pg_restore --clean --if-exists -U agentic -d agentic
systemctl --user start agentic-brain.service
On Docker, in agentic-os:
docker compose stop brain
docker compose run --rm -T --entrypoint bash backup \
/opt/agentic/restore.sh --force "$(cat .releases/pre-update-backup)"
docker compose up -d
The restore prints the row count of every table when it finishes.
Every page carries a Report a problem link. It builds a bundle of what the desk knows about the last few minutes — no mail bodies, no keys, no passwords — and shows it to you before anything leaves. Read it, then send it.
Tell us three things: what you pressed, what you expected, and what you saw. A screenshot of the page and the version from the footer answer most of it.