# Zylos core keeps one model loop alive in tmux under PM2

> Zylos core is a small JavaScript CLI that keeps a Claude Code or Codex session resident in tmux, supervised by PM2 and reachable through Telegram, Lark or a web console on port 3456. The mechanism is easy to read, but the Docker defaults turn permission prompts off and the non-interactive installer can report success after a partial failure.

**zylos-ai/zylos-core** — 🐙 Give your AI a life — open-source agent infrastructure for team collaboration.

- Repository: https://github.com/zylos-ai/zylos-core
- Website: https://zylos.ai
- Stars: 1,159 · Forks: 131
- Language: JavaScript
- License: MIT
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/zylos-ai-zylos-core

## tmux plus five PM2 services is the whole runtime

A comment in the Dockerfile gives the most direct description of what actually runs. Inside it, five PM2-managed services start: scheduler, web-console, c4-dispatcher, activity-monitor, channels. The model call is not among them. Claude Code lives inside a persistent tmux session, and c4-dispatcher is the bridge that feeds heartbeat and message commands into that session, which is why the agent is a long-lived terminal process instead of a request handler.

`pm2@latest` is installed globally in the image, and tmux arrives through apt beside git, curl, bash, ca-certificates and procps, with dnsutils there for `zylos doctor` network checks. Building on node:22-slim, it drops to a non-root user named zylos, with `NPM_CONFIG_PREFIX` pointed at `/home/zylos/.npm-global`. None of that is novel on its own. Worth sits instead in the supervision layer and in the memory and scheduler work stacked on top of it, plus the channel plumbing that lets a message reach the session at all.

## zylos init resolves flags, environment variables and .env in order

The installer is one pipe. It fetches scripts/install.sh from the main branch, installs git, tmux, Node.js and the zylos CLI, then runs `zylos init` on your behalf.

```bash
curl -fsSL https://raw.githubusercontent.com/zylos-ai/zylos-core/main/scripts/install.sh | bash
```

The setup step is where the real decisions happen, and each one can arrive by one of four routes. Resolution order runs CLI flag, then environment variable, then an existing `.env`, then the interactive prompt. That means a value already written into `.env` by an earlier run is honoured unless you override it on the command line, which matters on a host you re-image often.

```bash
curl -fsSL https://raw.githubusercontent.com/zylos-ai/zylos-core/main/scripts/install.sh | bash -s -- \
  -y \
  --setup-token sk-ant-oat01-xxx \
  --timezone Asia/Shanghai \
  --domain agent.example.com \
  --https \
  --caddy \
  --web-password MySecurePass123
```

In that example the runtime stays at its `claude` default, the timezone is pinned, and passing a domain switches HTTPS on and pulls in Caddy as the reverse proxy. Leave the domain out and TLS never turns on, because `--https` is the documented default only when a domain is set. Pass `--runtime` a value of `codex` instead and the host also needs Codex CLI `v0.129.0` or newer, `0.146` recommended, plus an OpenAI key handed over as `--codex-api-key`.

Automation gets three answers, not two. A zero status means success, one means a fatal error such as an invalid token, and two means partial success, spelled out as a Caddy download that failed while everything else completed. A script that checks only for a zero status will read a half-installed host as a healthy one.

## The manual npm path asks for Node 20 while engines ask for 20.20.0

If a modern Node is already present, a manual route skips the shell installer completely.

```bash
npm install -g --install-links https://github.com/zylos-ai/zylos-core
zylos init
```

README wording covers anyone on `Node.js >= 20`, but package.json pins `engines` to node `>=20.20.0`. Those two thresholds are not the same, and the gap stays invisible until npm rejects a runtime the README called good enough. A one-line version check settles it before you rely on the path.

`--install-links` is needed because the package is consumed from a git URL rather than a registry tarball, and `.npmignore` sits at the repository root to keep the packaged tree lean. The same choice is where the weakness lives: this install resolves the default branch, not a fixed version, so two people running the manual command on different days can end up with different code underneath them. Pin a tag or a commit in the URL if you need a repeatable environment. Nothing in the README explains that difference.

## Windows and NAS installs run through Claude Code over SSH

Installation assumes a Linux server or a Mac with a shell it can write to. Windows, ChromeOS and NAS boxes get no native build; instead you run Claude Code locally, open an SSH session to the Linux or macOS machine, and let the agent carry the installation across that connection.

```bash
# From your local machine (any OS that runs Claude Code)
claude --ssh user@your-linux-server
```

The session then runs Claude on the remote machine, and the prompt `Install Zylos on this machine` hands the whole setup to the model. You can also paste the same curl pipe into that SSH session by hand when you would rather watch each step.

That step deserves a closer look before anyone builds a rollout around it. The path is presented as the way to reach unsupported platforms, yet the installation work is delegated to a model holding your credentials, and no verification or rollback step is described anywhere along it. Treat it as an unattended change to a production host, read scripts/install.sh first, and run it on a machine you can rebuild.

## The Docker image ships with permission prompts switched off

The published image starts the agent with permission checks disabled. docker-compose.yml sets CLAUDE_BYPASS_PERMISSIONS to true unless you override it, so tool calls run inside the container without approval steps, and that default deserves review before anything is exposed.

```bash
docker run -d --name zylos \
  -e CLAUDE_CODE_OAUTH_TOKEN=YOUR_TOKEN_HERE \
  -p 3456:3456 \
  -v zylos-data:/home/zylos/zylos \
  -v claude-config:/home/zylos/.claude \
  ghcr.io/zylos-ai/zylos-core:latest
```

Two named volumes persist the agent data directory and the Claude config directory, and a single published port carries the web console. `WEB_CONSOLE_BIND` is set to 0.0.0.0 inside the container, which is fine behind a published port and wrong the moment that container joins a shared network. Password handling is a search rather than a lookup: the installer auto-generates the console password, and the documented way back to it is a log grep.

```bash
docker logs zylos | grep -A2 "Web Console"
```

Read that password as a log-resident secret. Setting `ZYLOS_WEB_PASSWORD` yourself keeps it out of container output and out of whatever ships those logs.

## Compose lists two auth paths and says nothing about Codex

Docker Compose draws a narrower line than the installer. Its authentication block is marked required, choose ONE, and offers a Claude Code OAuth token for Pro or Max subscriptions, or an Anthropic API key billed on usage. The installer documents a third route for the Codex runtime through `--codex-api-key` and the OPENAI_API_KEY variable, and no such line exists in the compose environment block. A Codex deployment therefore means editing compose by hand, and the two files do not tell the same story about which credentials are supported.

```yaml
    environment:
      CLAUDE_CODE_OAUTH_TOKEN: ${CLAUDE_CODE_OAUTH_TOKEN:-}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
      TZ: ${TZ:-UTC}
      CLAUDE_BYPASS_PERMISSIONS: ${CLAUDE_BYPASS_PERMISSIONS:-true}
      WEB_CONSOLE_BIND: "0.0.0.0"
      ZYLOS_WEB_PASSWORD: ${ZYLOS_WEB_PASSWORD:-}
```

Every value falls back to an empty string, so a missing token yields a container that starts cleanly and fails later at the point of use instead of refusing to boot. Two ports are mapped, WEB_CONSOLE_PORT defaulting to 3456 for the console and HTTP_PORT defaulting to 8080 for the optional Caddy proxy, which stays off until you set CADDY_ENABLED=true. Upgrades run `docker compose pull && docker compose up -d`, and that pull takes the latest tag rather than anything pinned.

## Three runtime dependencies wrap an external model CLI

package.json is deliberately thin for something that behaves like infrastructure. Runtime dependencies are dotenv `^16.4.7` for environment files, js-yaml `^4.3.1` for configuration and smol-toml `^1.6.1` for whatever TOML the skills read. There is no database client, no queue and no model SDK. The package is an ES module whose entry point is cli/zylos.js, exposed on PATH as zylos.

```json
  "dependencies": {
    "dotenv": "^16.4.7",
    "js-yaml": "^4.3.1",
    "smol-toml": "^1.6.1"
  }
```

The consequence is that the heavy work happens elsewhere. tmux holds the session, PM2 supervises the services, and the request itself is made by Claude Code or the Codex CLI, never by this package. Switching runtimes changes which CLI must exist on the host and leaves the service list untouched.

The layout backs that up. Root entries include cli/, scripts/, skills/, templates/, data/, docs/ and test/, alongside two files that carry more weight than usual: CLAUDE.md, the instruction file the agent reads about itself, and registry.json. The skills directory plus the pretest step `node scripts/install-skill-deps.js` point at a skill catalogue shipped with the agent, and jest at `^30.3.0` runs next to a plain Node runner through scripts/run-node-tests.js.

## Telegram and Lark tokens exist in compose with no setup path

Channels are the thinnest part of the documentation. The introduction promises communication through Telegram, Lark or a web console, and docker-compose.yml carries TELEGRAM_BOT_TOKEN, LARK_APP_ID and LARK_APP_SECRET as optional entries. No setup step accompanies them: not how to obtain the values, not which BotFather flow creates the first one, not what a Lark app must be allowed to do. The channel services start under PM2 regardless, so a missing token surfaces as a listener with nobody on the other end rather than a clear configuration error.

Scheduler coverage is thinner still. It is named in the Dockerfile comment and counted among the PM2 services, yet no cron syntax, schedule file location or history view is given. The web console is the better documented surface, since its port and password retrieval are both written down.

Upgrades go through `zylos upgrade`, and CI, Kubernetes and shared end-to-end environments are advised to provide GITHUB_TOKEN so `zylos add` and `zylos upgrade` use GitHub's authenticated API quota instead of the shared unauthenticated one, with configuration examples and fallback behaviour in docs/github-authentication.md. Zylos core ships under the MIT licence and the last push to main was on 2026-09-15, so that branch carries commits past the published v0.8.1 tag of 2026-09-09, and both the git-URL install and the latest image tag follow the moving head.

## Conclusion

Zylos core fits operators who already hold a Linux box and want one Claude or Codex session to survive reboots and answer from a phone. It does not fit a hardened multi-tenant agent service: the Compose file sets CLAUDE_BYPASS_PERMISSIONS to true and the console password is read back out of the container log. Before adopting, confirm the host runs Node >=20.20.0, decide whether main or a pinned tag is your install source, and check that your Telegram or Lark token actually reaches the session, since no step for obtaining either one is written down.

## FAQ

### What does Zylos core actually run on the host?

A persistent tmux session holding the Claude Code or Codex CLI, supervised by PM2 together with the scheduler, web-console, c4-dispatcher, activity-monitor and channels services listed in the Dockerfile.

### How do I install Zylos core on Windows or on a NAS?

There is no native installer for those platforms. The documented route is `claude --ssh user@your-linux-server` followed by asking the remote Claude session to install Zylos.

### Which credentials does the Zylos core Docker setup accept?

docker-compose.yml offers a Claude Code OAuth token or an Anthropic API key and marks one of them required. The Codex route through `--codex-api-key` appears in the installer flags but has no line in the compose environment block.

### What port serves the Zylos core web console and how do I find the password?

Port 3456, mapped as `${WEB_CONSOLE_PORT:-3456}:3456`. The password is auto-generated at install time and the README shows `docker logs zylos | grep -A2 "Web Console"` to read it back.

### Which Node.js version does Zylos core require?

package.json declares `engines` as node `>=20.20.0`. The manual npm route is described for anyone on `Node.js >= 20`, so the documented floor is looser than the enforced one.

### Is the agent's tool approval required inside the Zylos core container?

Not by default. docker-compose.yml sets CLAUDE_BYPASS_PERMISSIONS to true unless you override it, so tool calls proceed without approval prompts.

## Sources

- [License: MIT](https://github.com/zylos-ai/zylos-core/blob/main/LICENSE)
- [Project website](https://zylos.ai)
- [README](https://github.com/zylos-ai/zylos-core/blob/main/README.md)
- [Releases](https://github.com/zylos-ai/zylos-core/releases)
- [zylos-ai/zylos-core on GitHub](https://github.com/zylos-ai/zylos-core)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zylos-ai-zylos-core
