Pipe

Workers, targets, routines, watching, diagrams and server history

Pipe v0.50.5

This document describes the features that set Pipe apart from general-purpose agents: an agent that watches the server by itself, sees its architecture and manages many machines at once.


Pipe writes first

You don't have to ask what is going on. Pipe writes on its own – on Telegram, in pipe web and in the CLI – when something needs attention, and gives a button that deals with it right away. Everything it sends also lands in the inbox (backend/data/feed.json), so nothing is lost when nobody was connected.

MessageWhenButtons
Alertthe problem is already there: disk, memory, container, site, certificate, vulnerabilitiesFree up space, Patch, Investigate
Findingsa critical problem: Pipe checks the cause itself (a worker, reads only) and writes what it foundFix it: routine fixes run at once, anything important waits for your yes
Suggestiona problem is coming or something can be improved (table below)the action, Later, Fewer like this
Morning reportat DIGEST_TIME--
Resolvedthe problem went away--

Suggestions are computed without a language model, from data Pipe collects anyway (every INSIGHTS_INTERVAL, 1 h):

SuggestionFrom
Disk / will be full in about 4 daysthe growth rate from the metric history (a regression over the last 48 h)
I can free about 6 GBuntagged Docker images, build cache, downloaded apt packages
Container X restarted 4 times within a daythe restart counter (restart loops raise an alert)
The server has been waiting 5 days for a reboot/run/reboot-required
Memory keeps growingRAM use growing for a day (a possible leak)

Investigating on its own. When a critical problem appears (a container stopped, a site does not answer, the disk is full), Pipe does not wait for Investigate: a worker checks the logs, resources and the changes of the last day (reads only), and the findings arrive right after the alert, with a Fix it button. AUTO_INVESTIGATE=critical (default), all or off; at most AUTO_INVESTIGATE_DAILY (4) investigations a day, the same problem at most once every 6 h.

When to notify. The level (NOTIFY_LEVEL): all – everything; important (default) – problems, warnings, the report and important suggestions, informational ones wait in the inbox; critical – only what cannot wait. Quiet hours (QUIET_HOURS=22:00-07:00): at night only critical matters come through, the rest arrives in the morning together with the report. At most INSIGHTS_PER_DAY (3) suggestions a day with a notification. Fewer like this mutes that kind of suggestion (it stays in the inbox without a notification), Later snoozes it for a day. You can change the same settings while Pipe runs: /notifications (Telegram – with buttons, CLI – /notifications important quiet 22-7 daily 3, pipe web – the bell on the dashboard).


The workshop: projects Pipe sets up on its own

Write *"set this app up for me: https://github.com/acme/notebook"* or "write a script that counts the orders in the logs", and Pipe sees it through on its own. Projects live in one host directory – the workshop (WORKSPACE_DIR, /root/pipe-workspace by default), each in its own subdirectory and in backend/data/projects.json.

StepWhat Pipe doesAsks?
clonegit clone --depth 1 from an https or git@ address (the server's SSH key); no hooks, submodules, LFS; a repository that is too big (PROJECT_MAX_MB) is removedno
inspectwhat it is and how to run it – without a model: compose, Dockerfile, Node, Python (Django, FastAPI, Flask, Streamlit), Go, PHP, a static site; ports, needed variables, README and AGENTS.mdno
writing fileswrite_file in the workshop directory (e.g. a Dockerfile, a script)no
execa command in a one-off container: only the project directory as /work, no docker.sock and no privileges (--cap-drop ALL, no-new-privileges), memory, process and time limitsno*
runa long-running service: container pipe-<name> (or compose project pipe-<name>), the port on 127.0.0.1 onlyno – the plan and progress show in the conversation
logs, stop, start, pulllogs, stopping, resuming, git pull --ff-onlyno
removethe directory, containers and the built imageyes

\* After reading content from outside (the README of a cloned repository, a web page) Pipe asks for approval of exec until your next message – like YOLO: text from someone else's repository cannot run code by itself.


Saving tokens

Watching, suggestions, reports, /status, /changes, /audit, /cleanup and the dashboard work without a language model – zero tokens. When the conversation does go to the model, Pipe sends less:


Diagrams

The agent draws diagrams in Mermaid syntax and sends them as an image:

The diagram tool has two modes:

ModeWhenWhere the data comes from
infra"show the architecture", "what is running here", /mapautomatic discovery (below)
mermaidflows, procedures, dependencies, "draw how the deploy works"source written by the agent

The infrastructure map (/map works without the LLM, so it is fast and free) combines:

A stopped or unhealthy container is red. Labels are escaped, so a container name or a domain can't break the diagram syntax.

Rendering: mermaidx – the real Mermaid library in an embedded QuickJS + resvg. No browser, no Node.js and no sending the diagram source to an external service (unlike mermaid.ink/kroki). A syntax error goes back to the model, which fixes the source.


DIRECTORY – map of directories

A complement to SERVER.md: instead of prose, a list of concrete places with a one-sentence description.

- /srv/shop [repo] shop (Next.js), deployed with compose (remote https://github.com/acme/shop.git, branch main)
- /srv/shop/docker-compose.yml [compose] docker compose project 'shop'
- /var/backups/pg [backup] nightly Postgres dumps, 7-day rotation

VIBE – conversation style

Over time the agent adapts to how you write and what you expect.


Remote targets

Experimental: it works and has tests, but has seen less use on real servers than the rest of Pipe. If something is off, please open an issue.

A target is a place where Pipe runs commands without installing anything on the other side:

KindHow Pipe runs a commandFields
sshssh -o BatchMode=yes ... host -- 'command'host, user, port, identity_file
dockerdocker exec container sh -c 'command'container
kubernetes (cluster)kubectl --context C --namespace N ...context, namespace
kubernetes (pod)kubectl exec pod -- sh -c 'command'pod, pod_container
localthis host (always available)--

Example: "add server 10.0.0.5 as db-backup, ssh, user root" → YES → "check connectivity".


Workers

Experimental: it works and has tests, but has seen less use on real servers than the rest of Pipe. If something is off, please open an issue.

A worker is a sub-agent the agent talks to, not you. The delegate tool sends several at once:

You:    find out why load is rising on both www servers
Agent:  delegate [web-1: "load, top processes, nginx logs from the last hour"],
                 [web-2: the same]
          › web-1: start (web-1)
          › web-1 $ uptime
          › web-2 $ ps aux --sort=-%cpu | head
          › web-1: done (4 commands)
Agent:  On web-1 the load comes from a backup cron at 14:00, on web-2 from php-fpm; I suggest ...

Routines

Tasks the agent runs by itself on a schedule and reports on:

"every day at 7 check the backups in /var/backups and certificate validity, only write when something is wrong"


Reminders

A routine repeats; a reminder fires once, at the given time, and disappears:

"message me in 10 seconds" · "remind me tomorrow at 9 to renew the domain" · "in an hour check whether the backup finished"

The agent can't wait in the middle of an answer. Without this tool a promise like "I'll get back to you later" would be empty, so the prompt forbids it when nothing was scheduled.


Watching

Deterministic checks every WATCH_INTERVAL (120 s), without the LLM and at no cost:

CheckThresholdLevel
Usage of each host diskWATCH_DISK_PCT (90%), >= 97% criticalwarning / critical
RAMWATCH_MEM_PCT (92%)warning
5-minute load averagecores x WATCH_LOAD_FACTOR (2)warning
Container in a restart loop--critical
Container unhealthy--warning
A container that was running stopped--critical (once)
New public port on the hostcompared to the previous checkwarning (once)

What changed – the server's time machine

The most common question during an outage: "what changed that made it stop working?". Pipe knows the answer, because every SNAPSHOT_INTERVAL (1 h) it takes a snapshot of the host, with plain file reads and docker inspect, no LLM:

SectionWhat you see
Systemkernel, server reboot (new boot)
Packagesinstalls, upgrades, removals (dpkg, apk)
Containersnew and removed, image change or a new image version under the same tag, stops
Portsnew and closed TCP ports on the host
Servicesenabled/disabled systemd services, changed unit files
Cronadded and removed lines (secrets redacted)
Accountsnew accounts with a shell or uid 0
SSH keysnew keys in authorized_keys (fingerprint + comment; the key itself is not stored)
Configurationsshd, sudoers, nginx, Caddy, HAProxy, fstab, hosts, daemon.json, project compose files

Charts

On every check (every 2 min) the watcher saves a sample: load, RAM, swap, disk usage. The history (METRICS_KEEP_DAYS, 8 days) gives you charts without Prometheus:

A chart is a Mermaid xychart-beta rendered by the same engine as diagrams (offline).


Monitoring without configuration

Pipe knows what is on the server, so it knows what to check by itself, every CHECKS_INTERVAL (1 h):

CheckHow Pipe knows what to checkAlert
TLS certificate validitydomains from nginx, Caddy, Traefik labels / VIRTUAL_HOSTWATCH_CERT_DAYS (14) days before expiry; critical 3 days before or when the certificate is invalid
HTTPS responsethe same domains5xx or no answer twice in a row (a brief 502 during a deploy doesn't alert)
DNSthe same domainsthe domain doesn't resolve; in /health also whether it points at this server
Backup freshness[backup] entries in DIRECTORYthe newest file older than WATCH_BACKUP_HOURS (26 h), an empty or missing directory

Morning report

Every day at DIGEST_TIME (7:00 server time; off turns it off) a report arrives on Telegram, without the LLM:

A load chart for the last day is attached. /report – the report on demand.


LLM costs

Every request to the provider (conversation, workers, routines, VIBE) is counted in backend/data/usage.json.


Changes with a safety fuse and /undo

Pipe makes routine changes itself: restarting and reloading services, docker compose up -d, config edits, packages from the system repository, git pull. They go through the same plan as a change after YES, and the conversation shows them marked without asking. It always asks about deleting data, access to the server (SSH, users, firewall), autostart (cron, systemd), code and commands it does not recognise – the full list: docs/security.md (Autonomy). AUTONOMY=0 brings back the question for every change.

Every change gets a plan – for important ones shown in the confirmation before you press YES:

I need your approval
$ sed -i 's/8080/8081/' /etc/nginx/sites-enabled/shop && systemctl reload nginx
Safety fuse:
- backup before the change: /etc/nginx/sites-enabled/shop
- check before (failure = I won't run it): nginx -t
- verify after: nginx -t; nginx service active; sites that work now must still work after the change
- if verification fails: I restore the files from the backup automatically and reload again
- undo later: /undo
StepWhat Pipe does
Backupfiles changed by sed -i, tee, >/>>, cp, mv, rm, chmod/chown, write_file; the repository HEAD for git pull/checkout/reset/commit; the crontab; Pipe's memory files when targets, routines, skills and SERVER.md change
Beforeconfig validation before a (re)load: nginx -t, sshd -t (protects against locking yourself out of SSH), caddy validate, apachectl configtest, haproxy -c, postfix check, docker compose config -q; in a container docker exec <nginx> nginx -t. A failed check = the operation doesn't run
Afterservice active, container running and not unhealthy, all containers of the compose project came up, the changed file passes validation (nginx, sshd, sudoers visudo -c, fstab, compose, JSON), sites that answered before the change answer after it
Restorefailed verification of a config file change → the backup comes back automatically (SAFE_AUTO_ROLLBACK=1) and the service is reloaded again. Other operations: the result goes to the agent, and you can /undo

The plan comes from analysing the command in code (no LLM); exactly the plan you saw is executed. A program that doesn't exist where Pipe runs (for example sshd in a container) is skipped and reported as not checked, and the plan says so outright.

Journal and undo. /journal shows recent changes (backend/data/journal/, 50 entries, 14 days). /undo (or "undo the last change") shows file diffs and inverse commands (docker stop → docker start, systemctl disable → enable, git pull → git reset --keep <previous HEAD>, docker compose down → up -d) and waits for YES. /undo works without the LLM, so it also helps when the provider doesn't answer or the daily limit ran out. An undo goes into the journal too, so it can be undone.

What Pipe doesn't undo automatically (and says so in the plan): package installs, changes in a Kubernetes cluster, removed containers and volumes, git clean, directories removed with rm -r, previous image versions.


Security audit and sentinel

/audit (or "how secure is this server?") – a score of 0-100 without the LLM, each point with a fix:

AreaWhat it checks
SSHpassword login (taking sshd_config.d and the "first value wins" rule into account), root with a password, keys
Firewallufw, firewalld, nftables, iptables (a firewall in the provider's panel is invisible to Pipe, and it says so)
Portsdatabases, Redis, Elasticsearch, Docker/Kubernetes API on a public address; a port published by a container gets a fix in compose, because Docker bypasses ufw
Containersprivileged, mounted docker.sock, host network
Accountsuid 0 other than root, accounts without a password (only account names are read from /etc/shadow)
Updatesunattended-upgrades, pending security fixes, reboot required
Vulnerabilitiesknown vulnerabilities with a fix in system packages, container images, remote servers and applications (OSV.dev, CISA KEV) – see below
Protectionfail2ban / CrowdSec (more important when SSH accepts passwords)
Secretsworld-readable .env files in application directories from DIRECTORY
Hygienetime sync, swap on a small machine, certificates

"fix 1" – the agent applies the fix with a normal tool, so you get it for approval with a safety-fuse plan (backup, sshd -t before reloading, /undo). An SSH fix goes into its own sshd_config.d/00-pipe-*.conf file, so it wins over cloud-init settings. Without a key in authorized_keys Pipe doesn't propose disabling passwords; the key comes first (the harden-ssh skill). In docker mode the commands are marked in the host shell (the host file system is read-only).

The sentinel (every WATCH_INTERVAL, no LLM) compares accounts, SSH keys, SUID/SGID programs and authentication configs (sudoers, sshd, PAM, /etc/passwd, ld.so.preload). A new account with uid 0, a new key, a new SUID or ld.so.preload raises a critical alert. Changes made by Pipe (they are in the journal) don't alert. Neither do package updates: a SUID program or a config whose content matches the checksum in the package database (dpkg md5sums and Conffiles, apk) is a patch, not a swap. A binary swapped after installation still raises an alert.

SSH log (/var/log/auth.log or /var/log/secure, read incrementally): a series of failed logins, a successful password login from an address that was guessing passwords before, a login from a new address.

Package vulnerabilities (Debian, Ubuntu, Alpine). Pipe reads the package list from the dpkg/apk database (it runs nothing) and asks OSV.dev about known vulnerabilities for the exact versions. Only what has a fix counts – Pipe leaves out vulnerabilities without a fix, because a plain Debian has hundreds and nothing can be done about them. The report starts with "What to do about it": one line for the server, the images and the applications, each with a concrete step. Only package names and versions are sent to OSV; VULN_INTERVAL=0 turns the scan off.


The first 5 minutes

After installation, when the Telegram bot connects for the first time, Pipe writes by itself: a map of the server as an image, a security score with the three most important fixes and a list of what it watches from now on. The CLI shows the same on the first connection to a given server. No LLM.


Built-in skills

SkillWhat it does
nginx-vhosta new domain in nginx as a reverse proxy + a Let's Encrypt certificate
swapa swap file with an fstab entry (btrfs too)
fail2ban-sshfail2ban with an SSH jail, without banning your own address
backup-postgresa nightly pg_dump with rotation, a [backup] entry in DIRECTORY (the watcher checks freshness)
update-containerpull + up of a compose service with a plan to go back to the previous image
harden-sshdisabling SSH passwords in an order that won't lock you out
free-disk-spacewhat takes up the disk and a safe cleanup, step by step

Installed at startup into backend/data/skills/ like normal skills, so you have them in /skills and as commands (/nginx_vhost). You can edit and delete them: a new version from a Pipe update replaces only a skill you haven't changed, and doesn't bring back a deleted one.

Skills and plugins from other ecosystems. /download skill <owner/repo|URL> [filter] and /download plugin <...> (/pobierz) take ready sets from a git repository – the same SKILL.md format as Claude Code, and plugins in the Claude Code plugin layout (skills/, .mcp.json, marketplace). First a preview: which skills go into memory (text only, scripts are skipped), which MCP servers are added (in an isolated container, secrets to add to backend/.env as MCP_...) and what is skipped – with the reason (a skill that is too long, a server that needs the plugin's files, hooks, agents). Installation only after Install. The same name in several plugins gets the plugin's prefix (telegram-access). In a conversation: "download the skills from anthropics/skills".

With PIPE_LANG=pl the Polish versions are installed: aktualizuj-kontener, utwardz-ssh, wolne-miejsce (the other four have the same name in both languages).


Incident memory

Every resolved alert stays in backend/data/incidents.json: what happened, when and for how long, what the agent found after Investigate (or a worker for a webhook alert) and what helped, meaning the journal changes made while the problem lasted. No extra LLM requests.

When the same problem comes back, the Telegram alert has a line Previously: 2026-09-12 (40 min) — findings: nginx logs without rotation... — helped: journalctl --vacuum-size=200M, and the Investigate request gets this history: the agent first checks whether it is the same cause and proposes the fix that worked (still for approval). /incidents shows the history; the agent reaches for it by itself ("has this happened before?").


Alerts from outside (webhooks)

Pipe joins the monitoring you already have instead of replacing it:

# backend/.env
WEBHOOK_PORT=7380
WEBHOOK_TOKEN=$(openssl rand -hex 24)
SourceAddressConfiguration on the sender's side
Prometheus Alertmanager/hook/alertmanagerwebhook_configs: - url: http://127.0.0.1:7380/hook/alertmanager + http_config.authorization.credentials: <token>
Grafana/hook/grafanaa Webhook contact point, header Authorization: Bearer <token>
Uptime Kuma/hook/uptime-kuma?token=<token>a Webhook notification (application/json)
GitHub/hook/githuba repository webhook, Secret = token; events Workflow runs, Deployment statuses
Anything else/hook/generic{"title", "message", "severity", "status": "firing|resolved", "name"}

The alert goes to the watcher (it disappears on resolved) and to Telegram with an Investigate button. With WEBHOOK_INVESTIGATE=1 a worker investigates a new alert right away (reads only: services, containers, logs, resources, changes in the last day), the same way as problems Pipe finds itself: the findings arrive shortly after the alert with a Fix it button; the same alert at most once in 6 h, 10 webhook investigations a day (separate from AUTO_INVESTIGATE_DAILY).

In Docker the port is published only on 127.0.0.1:7380; senders outside the server go through a reverse proxy with TLS.


Attachments: files, screenshots, logs

You can send the agent a file together with a question: a screenshot of an error, a log, an nginx config. On Telegram a photo or a document (the caption is the question, an album of several photos goes as one message), in pipe web the paperclip, dragging a file onto the window or pasting a screenshot from the clipboard (thumbnails before sending), in the CLI /file <path> [question] or @path in a normal message (Tab completes paths).

The backend (core/attachments.py) recognises a file by its content, not its name:

Limits: 10 MB per file, 20 MB and 10 files per message. Attachment content is data, not instructions: YOLO mode is paused until the next message, just like after reading a web page.

Voice messages

Experimental: it works and has tests, but has seen less use on real servers than the rest of Pipe. If something is off, please open an issue.

On Telegram just record a message: "find out why the shop is down". The bot replies I heard: ... and passes the text to the agent. In pipe web there is a microphone button: the browser turns the recording (up to 5 minutes) into 16 kHz WAV and sends it for transcription, and the text goes to the agent. Transcription: with LLM_PROVIDER=gemini the Gemini model itself does it (audio input), with openai or groq that provider's Whisper endpoint does; in both cases without configuration. With other providers set STT_BASE_URL, STT_API_KEY, STT_MODEL (for example the free Groq whisper-large-v3-turbo or a local faster-whisper); STT_* always takes precedence.


Roles and multiple users

RoleCanCan't
admineverything--
viewerconversation, diagnosis, reads, charts, reports, audit, alertsapprove changes, /undo, write Pipe's memory (SERVER.md, skills, targets, routines)

Web interface and live map

pipe web opens an interface with the conversation and a side panel in the browser. It runs on your computer, through the same tunnel as the CLI.

The dashboard (the panel's first tab) answers "is everything working?" without a model: a headline in plain words ("Everything works" or "Needs attention: ..."), rings for CPU, memory and disk with the last day's trend, the From Pipe inbox (messages with action buttons, new ones marked with a dot; the bell in the top bar shows how many), cards of the workshop projects (logs, stopping, From GitHub) and today's model usage (tokens, cost, cache, how much Pipe saved). The bell next to the inbox opens the notification settings. Resources, the inbox and projects fold with a click on the section header (folded resources stay as percentages on one line), and a single message with a click on its title. The browser remembers it; the bell in the top bar and /news unfold the inbox. The empty conversation screen suggests tasks in plain words (Is everything working?, Free up disk space, Run an app from GitHub, Write me a script); the ones marked no AI work instantly and cost nothing.

The map builds itself from what Pipe knows about the infrastructure; nothing to draw or configure:

LevelWhat you see
Serversthis server and remote targets (SSH, containers, clusters)
Inside a serverinternet, reverse proxy, compose projects, standalone containers, services and ports
Projectthe project's containers, their ports and dependencies (app → database), the project directory

An action is assigned to an element from the command itself: a container name, a systemd unit, a file path, a project directory. When that's not possible (for example an arbitrary script), the action is shown on the whole server. Searching and reading pages (web_search, web_fetch, software_info) lights up the Internet element.

On a phone chat and dashboard switch with the buttons in the top bar, and the view, language and theme are in the ... menu. On a narrow screen the diagram is laid out top to bottom (readable without zooming): one finger pans, two zoom, a double tap on empty space zooms in, the frame button goes back to the whole picture. Element details slide up from the bottom. The message field stays above the keyboard.

The side panel takes half the screen by default. You can drag its edge to make it narrower or wider, up to half the screen; a double click on the edge restores the default width, and dragging it to the right edge of the window hides the panel. A button in its tab bar hides it too; a button in the top bar hides it and brings it back. With the panel hidden the conversation takes the whole window (the content in a readable column in the middle), and a command or a notification that opens a tab (for example /alerts) brings the panel back by itself. The width and state of the panel are kept in the browser. From the keyboard: focus the edge, the arrows change the width, Enter hides it.

Agent steps in the conversation. Under the answer you see what the agent did: every command (or a description, e.g. reads /etc/nginx/nginx.conf) and a few lines of its output – on an error the error lines, on success the end. Full output expands up to 60 lines. While the agent works the block is open; after the answer it collapses to Steps: N (unless something failed or an approval card is waiting). A change made without asking is marked without asking. It is the same output the model got, with secrets hidden. The simple view has no such blocks. The CLI shows the same as grey lines under the command, Telegram in the progress message.

Changes. Before you approve a file write you see the diff in the confirmation card. After it runs, the "before → after" diff appears in the conversation and in the Changes tab, also when a command changed the file (for example sed), because the safety fuse takes a backup. Secrets in diffs are redacted. Every entry has an undo button. There are no diffs for directories and data in databases.

Commands and skills. Typing / in the message box opens the list of commands (/status, /news, /report, /audit...) and skills with descriptions; pick with the arrows or a click, Tab completes the command so you can add arguments. /skills shows saved procedures in the conversation, with a content preview and a run button. The panel has three tabs: Dashboard, Map (a dot next to the name while the agent is working) and Changes; the last one you picked stays in the browser.

LLM provider. On the first visit the page asks which provider Pipe should work with: you pick it from the list, paste the API key (it is checked with the provider before it is saved) and pick a model from its current list. You can skip this screen; the provider from the installation stays. Above the message box there is a switcher showing who answers; it opens the list of providers that have a key and switches without a restart, in the middle of a conversation. /provider opens the screen again (another key, a different model, removing a key). Keys are stored on the server in DATA_DIR/llm_keys.json (0600) and never go back to the browser. The choice applies to the whole agent: after switching, Telegram, the CLI, routines and workers use the new provider too; WORKER_MODEL and prices from .env apply only to the base provider. You can also put several keys straight into backend/.env (OPENAI_API_KEY, GROQ_API_KEY, ANTHROPIC_API_KEY...).

Pipe in the browser on your phone and computer (/web)

pipe web runs on your computer through an SSH tunnel. To open Pipe on your phone or on any computer without a tunnel, write one command in Telegram, the CLI or pipe web:

/web on

Pipe puts an HTTPS server in front of itself (Caddy in the pipe-web container) and answers with an address, e.g. https://203-0-113-9.sslip.io – a free name that points to your server's IP, so the Let's Encrypt certificate is issued on its own, without buying a domain. With your own domain: /web on pipe.mycompany.com (the DNS record must already point to the server). The port is 443, and when it is taken (e.g. by nginx) – 8443; your own: /web on 9443.

On mikr.us Pipe recognises the server (the /klucz_api file) and uses mikr.us subdomains: the page listens on IPv6 and mikr.us itself gives the address https://<server>-7443.mikrus.cloud with a certificate. Nothing to open or sign up for, the address does not change, and traffic goes through nobody but mikr.us. To force it: /web on mikrus.

When the provider lets no traffic in on 80/443 (other servers on a shared address, a home server behind a router, a cloud panel with blocked ports), no certificate can be issued. Pipe checks this itself: if the public address is not the server's own address or the certificate is not issued, it switches to a Cloudflare tunnel and answers with an address like https://quiet-river-1a2b.trycloudflare.com. The tunnel is a cloudflared container that connects to Cloudflare on its own – no port to open and no account. The price: traffic passes through Cloudflare (it encrypts the connection to the browser), a quick tunnel has no uptime guarantee, notifications on the page arrive every few seconds instead of instantly, and the address changes after a server restart – Pipe notices the new one and sends it in a message (devices are let in once more then). To force it: /web on tunnel or /web on direct.

Before Pipe gives you the link, it opens it from the internet itself and checks that the access-request page answers. If not, it says why and shows the last lines from the Caddy or cloudflared logs.

How you get in. The page has no password and no secret link. You open the address, press Ask for access and see a code, e.g. K7P-4QX. At the same moment Pipe writes to you (Telegram, pipe web on another device, the CLI): "Someone wants to open Pipe in a browser – iPhone, Safari, IP ..., code K7P-4QX" with Let in, View only and Reject buttons (or you write /web approve K7P-4QX). You check that the code matches and press Let in – the page opens by itself. You let each device in once: the phone separately, the computer separately. After that it stays signed in for 30 days after its last use.

A device let in with View only has the viewer role: it chats, reads reports and the diagram, but changes nothing. Only an administrator can let devices in. The page on a phone is the same interface as pipe web – chat and dashboard switch with the buttons in the top bar. Added to the home screen (Safari: Share → Add to Home Screen, Chrome: Add to Home screen) it shows the Pipe icon.

When ufw runs on the server, Pipe opens the page's port in it (and 80 when the page is not on 443 – Let's Encrypt checks the server there while issuing the certificate). Pipe cannot see the hosting provider's firewall (cloud panel): if the page does not respond, Pipe says which port to open. Works in docker and native mode (Docker is needed – Caddy is a container), not in observe mode or Kubernetes (use an Ingress there). Security details: docs/security.md (Pipe on the internet).


Views: simple and technical

Pipe has two views, like one app for two jobs. Simple is for non-technical people: chat in plain words, reminders, files, skills and add-ons for external apps (MCP servers: GitHub, a calendar, email). The agent picks how to do the task itself, doesn't ask you to type commands and asks about important things with a plain yes/no question. Technical has the same plus the server: the live map, the change journal with undo, vulnerabilities and patching, containers, remote servers.

On the first run Pipe asks what you want to do. You can answer in a few words ("reminders and emails", "keep an eye on the shop server") or pick a view. The view is shared by the whole agent, like the language: /view simple, /view technical (/widok) in the CLI, in pipe web (also a button in the header) and on Telegram (buttons). Changing it in one channel switches the others. It is saved in DATA_DIR/view.json and only an admin changes it. Until someone chooses, the technical view applies, which is what Pipe showed so far.

WhereSimple viewTechnical view
pipe webDashboard (without charts and projects) and Add-ons tabs, everyday suggestionsDashboard, Map, Changes, Add-ons
CLI/help and / suggestions without server commandsall commands
Telegram/ menu, /help and keyboard: status, news, skills, helpmenu and keyboard as before
Agenta SIMPLE VIEW block in the prompt: plain words, no jargonunchanged

Server commands (/patch, /map, /audit and others) work in both views when typed; the simple view only doesn't suggest them. The Add-ons tab in pipe web gathers skills (run, preview), connected MCP servers and adding new ones from GitHub (/download, /mcp add).

First run without a server. pipe web without --host and without a saved connection opens a wizard: first the view, then the place. On this computer starts the backend in Docker from this repository (on Linux Pipe manages this computer, on Windows and macOS it works in its own Docker Desktop container, backend/docker-compose.desktop.yml). On my server connects over SSH with an address and a password or key, clones the repository into /opt/pipe and runs install-server.sh without questions (without a model; you add the API key on the page afterwards). The password stays in the pipe web process: it doesn't go to disk, to the backend or to the model. After a password login Pipe adds its own key on the server (~/.pipe/keys) and uses it from then on. The connection is saved in ~/.pipe/app.json, so the next pipe web and plain pipe connect on their own. The wizard comes back with pipe web --setup or by clicking the server address in the header.


Language: English or Polish

PIPE_LANG=en (default) or PIPE_LANG=pl switches the whole product, not just the language of the model's answers. You can also change the language while working, without a restart: /language pl / /language en (/jezyk) in the CLI, in pipe web (the command or the flag button in the header) and on Telegram. The choice is shared by the whole agent (the other channels switch by themselves), it is saved in DATA_DIR/language.json and holds until PIPE_LANG in .env is changed by hand. Only an admin can change it.

LayerWhat changes
Agentsystem prompt, tool descriptions, prompts for workers, routines, the server scan and VIBE
Messagesconfirmation questions, the safety-fuse plan, tool results, validation errors
Watchingalert titles, the morning report, /changes, /health, /audit with fixes, the welcome
MCPtitles and descriptions of the MCP server's tools, instructions for the external agent
ClientsCLI (--lang pl; without it the CLI and pipe web follow the server), the Telegram bot (/ menu, buttons, labels), the wizard and the installer
Skillsbuilt-in skills in the chosen language

Protocol tags ([POTWIERDZ], [BLAD], [ODMOWA], [PAMIEC], [ZREDAGOWANO: ...]) are the same in both languages: the server derives the frame status from them, and clients turn them into labels in their own language. Commands have English aliases (/report, /changes, /undo, ...) that work alongside the Polish ones.

Data saved earlier (SERVER.md, VIBE, your own skills, journal entries) stays in the language it was written in.


MCP – Pipe as a gateway for other agents and a client of other servers

Pipe as an MCP server

Claude Code, Cursor or your own agent gets Pipe's tools instead of a bare shell on production:

ToolWhat it does
run_commanda command through Pipe's classifier: a read runs right away; a change → admin approval, then the safety fuse and the journal; forbidden → refused
get_approvalthe state of an approval and its result (only for the agent that asked for it)
read_filea host file; files with secrets are not handed out, secrets in the content are redacted
server_status, server_changes, infra_map, health_checks, security_audit, journalPipe's knowledge about the server
ask_pipea question to the Pipe agent (it knows SERVER.md, DIRECTORY, the history), in a read-only role

The approval arrives on Telegram with the command and the safety-fuse plan (backup, nginx -t, verification) and Approve / Reject buttons; in the CLI, /approvals. The agent learns the result through get_approval.

Connecting from a laptop – the stdio bridge in the CLI, through the same SSH tunnel and tokens. A ready configuration for Claude Code, Claude Desktop, Cursor, Codex, Hermes Agent and OpenClaw: pipe connect <app> --host root@server (docs/cli.md). By hand:

{"mcpServers": {"pipe": {"command": "pipe", "args": ["--mcp", "--host", "root@server"],
                         "env": {"AGENT_TOKEN": "<token>"}}}}

A separate token for the agent: python3 -m backend.tokens add claude-code --role admin (or --role viewer: the agent only reads and can't ask for approvals). On the server – a Streamable HTTP endpoint: MCP_PORT=7381, http://127.0.0.1:7381/mcp, the token in Authorization: Bearer.

Pipe as an MCP client

Pipe also uses other MCP servers – e.g. email, a calendar, GitHub or Grafana – like its own tools.

The /mcp window. /mcp without arguments opens a window (like /mcp in Claude Code): in the CLI a frame you move through with the arrows, in pipe web a separate panel, on Telegram a list with buttons. It shows the connected servers with their state and, once you pick one, what exactly runs, its tools (which ones ask first) and Reconnect, Disable, Remove buttons.

Ready add-ons (backend/addons/, nothing is on by default):

Add-onWhat it givesWhat it needs
outlookmail, calendar and contacts of a Microsoft account (outlook.com, hotmail)nothing – after adding write "log me in to Outlook": a microsoft.com/devicelogin link and a code, the login survives restarts
microsoft365Outlook and Teams of a work or school accountthe same code login; some organisations require admin consent
googleGmail (reading, drafts), Calendar (read), Drive, Docs, Sheetsyour own OAuth client in Google Cloud and a refresh token – the window walks you through it
githubrepositories, issues, pull requests, Actionsa GitHub token (fine-grained)

Keys are entered in the /mcp window in pipe web or the CLI (a hidden field). They live in backend/data/mcp_secrets.json (0600) and never reach the model or the browser; Telegram does not take keys. It also works in a conversation: "connect my Outlook" – the agent knows the catalog (mcp_manage catalog) and adds the add-on after one YES. Add-ons with a login (state: true) get their own Docker volume for tokens; removing the add-on removes it together with the saved login. A new add-on is one JSON file – see backend/addons/README.md.

Your own server, outside the catalog:

/mcp add github npx -y @modelcontextprotocol/server-github GITHUB_PERSONAL_ACCESS_TOKEN=${MCP_GITHUB_TOKEN}
/mcp add fetch uvx mcp-server-fetch
/mcp add docs https://mcp.example.com/mcp TOKEN=${MCP_DOCS}
/mcp                      # list and state;  /mcp remove github;  /mcp reload

Or in a conversation: "connect the GitHub MCP server" – then one YES with a preview of what Pipe will run.

Where it runsnpx, uvx and a Docker image (including docker run ... from other apps' .mcp.json) – always in a separate container: no server files, no docker.sock, no privileges (--cap-drop ALL, user nobody), memory and process limits. The server sees only the network. HTTP servers – at the given address
Secretsonly as ${MCP_NAME} entered in the /mcp window or in backend/.env (e.g. MCP_GITHUB_TOKEN=...) – do not type the token in the chat; it reaches the container as a variable, never in the arguments
Approvalsa tool the server marks as a read – without asking; any other – a YES with the arguments shown
Model tokensthe server's tools are the group mcp:<name>, loaded on demand: an ordinary conversation does not send them, a message naming the server ("on github...") turns them on
Resultoutside content: YOLO is off for this turn; an argument with a secret does not go to the server
Observe mode, viewerread tools only; a new server cannot be added

Configuration: backend/data/mcp_servers.json (0600). A stdio server starts in ten seconds to a minute (npx / uvx install the package in the container); Pipe connects in the background after a restart.

Supported protocol versions (both directions): 2026-07-28 (server/discover, the version in _meta, Mcp-Method/Mcp-Name headers) and older ones with initialize (2025-11-25, 2025-06-18, 2025-03-26).


Internet without API keys

The agent goes to the web by itself when the answer depends on knowledge from outside the server; you don't have to ask it or give it any key:

Examples: "nginx keeps logging upstream prematurely closed, what is that?", "is our PostgreSQL still supported?", "does nginx on this server have known holes?". The agent gives its sources. The safeguards (secrets, addresses outside the search results, private network, pausing YOLO) are described in Security. WEB_SEARCH=off turns the internet off.

Updating Pipe itself

Type /update (in Polish /aktualizuj) or tell the agent "update yourself"; /update check only shows the versions and whether a newer one exists. The agent checks the remote repository and starts a separate helper container that runs git pull --ff-only in the install directory and swaps Pipe's services for the signed release image from GHCR (or, when there is none, rebuilds them: docker compose up -d --build). A separate container is needed because the agent can't replace the container it runs in; the process would die halfway.

Edit this page on GitHub