> ## Documentation Index
> Fetch the complete documentation index at: https://docs.echos.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup Fixes

> Configuration changes and fixes log.

# Setup Fixes & Configuration Changes

This document tracks configuration changes and fixes made to ensure the project runs correctly.

## July 2026 — Docker healthcheck decoupled from `ENABLE_WEB`

### Problem

The `echos` service's Docker healthcheck (`docker/docker-compose.yml`) fetched `http://localhost:3000/health`, which is served by the web interface (`packages/web`). On Telegram-only deployments (`ENABLE_WEB=false`), nothing listens on port 3000, so `docker compose ps` reported the container as permanently "unhealthy" regardless of actual health — masking real incidents (e.g. a genuinely wedged process after a host OOM kill looked no different from normal operation).

### Fix

* New `src/heartbeat.ts`: writes `/tmp/echos-heartbeat` every 15s, started unconditionally in `src/index.ts` alongside the other interfaces (not gated by `ENABLE_WEB`/`ENABLE_TELEGRAM`/`ENABLE_MCP`), stopped via `src/shutdown.ts`.
* `docker/docker-compose.yml`: healthcheck now checks the heartbeat file's age instead of fetching `/health`.

The web interface's own `GET /health` endpoint (`packages/web/src/index.ts`) is unchanged and still useful for external uptime monitoring when `ENABLE_WEB=true`.

## April 2026 — MCP Server Core (Phase 11.01)

### New env vars: `ENABLE_MCP`, `MCP_PORT`, `MCP_API_KEY`

A new MCP (Model Context Protocol) server is available. It is disabled by default.

```bash theme={null}
# .env
ENABLE_MCP=false        # Set to true to start the MCP server
MCP_PORT=3939           # Port for the MCP server (default: 3939)
MCP_API_KEY=            # Optional bearer token for authentication
```

When `ENABLE_MCP=true`, EchOS starts an HTTP server on `MCP_PORT` (default `3939`) that exposes seven knowledge tools via the Model Context Protocol. The server only binds to `127.0.0.1`.

If `MCP_API_KEY` is unset, all requests are accepted without authentication (suitable for localhost-only use). If set, all requests must include `Authorization: Bearer <MCP_API_KEY>`.

***

## March 2026 — SSHFS write access via Docker entrypoint ownership fix

### Problem

SSHFS mounts of the knowledge directory were read-only. Docker was writing volume files as the
`node` user (UID 1000), which differed from the server SSH user's UID on some VPS providers
(e.g. Oracle Cloud where the default `ubuntu` user is UID 1001).

### Fix

A Docker entrypoint script (`docker/entrypoint.sh`) now runs as root on startup, detects any
ownership mismatch in the data directories, corrects it, then drops to the `node` user via
`su-exec` before starting the app. This self-heals on every container start with no manual steps.

If upgrading from an older version, re-own once and restart:

```bash theme={null}
sudo chown -R $(id -u):$(id -g) ~/echos/data
cd ~/echos/docker && docker compose up -d --force-recreate
```

See [Remote Access → SSHFS write access](/remote-access#enabling-write-access) for full details.

## February 25, 2026 — Configurable Prompt Cache Retention

### New env var: `CACHE_RETENTION`

`pi-ai` already supported a `PI_CACHE_RETENTION` env var for controlling Anthropic prompt cache TTL (checked inside `resolveCacheRetention` in the provider), defaulting to `'short'` (5 min) unless `PI_CACHE_RETENTION=long` was set. EchOS now exposes this as a first-class Zod-validated config field, with a default of `'long'` and automatic enforcement of `'none'` for custom endpoints.

```bash theme={null}
CACHE_RETENTION=long    # default — 1h TTL; best cost savings for Anthropic
CACHE_RETENTION=short   # 5min TTL — matches old pi-ai default
CACHE_RETENTION=none    # disable prompt caching entirely
```

**What changed vs. the old `PI_CACHE_RETENTION` approach:**

* EchOS now defaults to `'long'` instead of pi-ai's `'short'`
* When `LLM_BASE_URL` is set (custom OpenAI-compatible endpoint), `cacheRetention` is forced to `'none'` regardless of this env var — those endpoints do not support Anthropic-style prompt caching
* The effective value is Zod-validated and logged at startup

```json theme={null}
{ "model": "...", "cacheRetention": "long", ... }
```

**Files changed:**

* `packages/shared/src/config/index.ts` — added `cacheRetention` Zod enum field; `CACHE_RETENTION` env var mapping
* `packages/core/src/agent/index.ts` — `cacheRetention?` in `AgentDeps`; `effectiveCacheRetention` computed (forced `'none'` for non-Anthropic); passed to `streamSimple`; logged at startup
* `src/index.ts` — passes `config.cacheRetention` into `AgentDeps`
* `.env.example` — `CACHE_RETENTION=long` added under LLM settings

***

## February 19, 2026 — Standalone CLI & Model Deprecation Fix

### `pnpm echos` — standalone three-mode CLI

`packages/cli/src/index.ts` is a standalone CLI binary — no daemon required.

**Three auto-detected modes:**

| Invocation                 | Mode             | Behaviour                                 |
| -------------------------- | ---------------- | ----------------------------------------- |
| `pnpm echos "query"`       | One-shot         | Boots agent, answers, exits               |
| `echo "msg" \| pnpm echos` | Pipe             | Reads stdin, answers in plain text, exits |
| `pnpm echos` (TTY)         | Interactive REPL | Persistent session with history           |

**Interactive REPL features:**

* History persisted to `~/.echos_history` (max 500 entries, restored on next run)
* Ctrl+C cancels in-flight response, re-prompts (does not exit)
* Tool calls shown in dim colour on TTY; plain text in pipe mode
* `exit` / `quit` / Ctrl+D exits cleanly

**Logging:** CLI defaults to `warn` log level — startup logs are suppressed. Override with `LOG_LEVEL=info pnpm echos`.

**No daemon required.** Both `pnpm start` (daemon) and `pnpm echos` (CLI) read from the same `./data/` directory. SQLite WAL mode makes concurrent access safe.

**Files changed:**

* `packages/cli/src/index.ts` — three-mode standalone CLI
* `packages/cli/package.json` — `"bin": { "echos": "./dist/index.js" }`
* `package.json` — `"echos": "tsx --env-file=.env packages/cli/src/index.ts"` script

### Default model updated — `claude-3-5-haiku-20241022` → `claude-haiku-4-5-20251001`

`claude-3-5-haiku-20241022` reached end-of-life on February 19, 2026. The Anthropic API returns empty responses for this model from today.

**Files changed:**

* `packages/shared/src/config/index.ts` — `defaultModel` default updated
* `packages/core/src/agent/index.ts` — in-code fallback updated; type cast cleaned up to `Parameters<typeof getModel>[1]`

**Action required if you have `DEFAULT_MODEL=claude-3-5-haiku-20241022` in `.env`:** update it to `claude-haiku-4-5-20251001`.

***

## February 19, 2026 — Web API Security & Experimental Interface Defaults

### Web marked experimental, disabled by default

Web UI is now clearly marked as experimental in the setup wizard with a warning before the interface selection step. It defaults to **off** (it was already off in the config schema, but the wizard was pre-selecting Web UI if no existing `.env` was found).

### New env var: `WEB_API_KEY`

All web API routes (except `GET /health`) now require `Authorization: Bearer <WEB_API_KEY>`.

```bash theme={null}
# .env
WEB_API_KEY=your_64_char_hex_key   # generate: openssl rand -hex 32
```

The setup wizard generates and stores this key automatically when you enable the web interface. On re-runs it reuses the existing key from `.env`.

If `WEB_API_KEY` is not set, the server starts but logs a warning and all routes are unauthenticated.

### `userId` now validated against `ALLOWED_USER_IDS` in web routes

All `/api/chat/*` routes now verify the `userId` in the request body is in `ALLOWED_USER_IDS`. Unknown user IDs return `403 Forbidden`. Previously the web API accepted any numeric `userId`.

### Web server binds to `127.0.0.1` (was `0.0.0.0`)

The Fastify server no longer listens on all interfaces. It is only reachable from localhost.

### CORS restricted to localhost origins

`origin: true` (allow all) replaced with a function that only allows `http(s)://localhost:*` and `http(s)://127.0.0.1:*`.

### Non-interactive wizard fix: `enableWeb` default

`ENABLE_WEB` in `--non-interactive` mode was defaulting to `true` unless explicitly set to `'false'`. Fixed to `false` unless explicitly set to `'true'`, matching the config schema default.

**Files changed:**

* `packages/shared/src/config/index.ts` — `webApiKey` field added
* `packages/web/src/index.ts` — auth hook, localhost bind, restricted CORS
* `packages/web/src/api/chat.ts` — `allowedUserIds` param, `403` on unknown userId
* `scripts/setup.ts` — experimental warning, fix defaults, generate/store `WEB_API_KEY`

***

## February 19, 2026 — Model Presets & Cross-Provider Handoffs

### New env vars: `MODEL_BALANCED`, `MODEL_DEEP`

Configure named model presets for on-the-fly switching during a session:

```bash theme={null}
MODEL_BALANCED=claude-sonnet-4-5     # default balanced preset
MODEL_DEEP=claude-opus-4-5           # default deep preset
```

**Format**: plain model ID (provider inferred from prefix) or `provider/model-id` for explicit provider:

```bash theme={null}
MODEL_BALANCED=openai/gpt-4o         # cross-provider: OpenAI
MODEL_DEEP=anthropic/claude-opus-4-5
```

**Built-in defaults** (used when env vars are not set):

| Preset     | Default model               |
| ---------- | --------------------------- |
| `fast`     | `claude-3-5-haiku-20241022` |
| `balanced` | `claude-sonnet-4-5`         |
| `deep`     | `claude-opus-4-5`           |

**Switching mid-session** (conversation history is preserved across model switches; thinking blocks from Claude are automatically converted to `<thinking>` tagged text for cross-provider compatibility):

* Telegram: `/model balanced`
* Web API: `POST /api/chat/model { "preset": "balanced", "userId": 123 }`

**Files changed:**

* `packages/shared/src/config/index.ts` — `modelBalanced`, `modelDeep` fields
* `packages/core/src/agent/model-resolver.ts` — `resolveModel(spec)`, `MODEL_PRESETS`, `ModelPreset`
* `packages/core/src/agent/index.ts` — `modelPresets?` in `AgentDeps`
* `packages/core/src/index.ts` — exports resolver + preset types
* `src/index.ts` — passes presets into `AgentDeps`
* `packages/telegram/src/index.ts` — `/model` command
* `packages/web/src/api/chat.ts` — `POST /api/chat/model` endpoint

***

## February 19, 2026 — Configurable Thinking Level

### New env var: `THINKING_LEVEL`

Controls the LLM reasoning depth. Defaults to `off`.

```bash theme={null}
THINKING_LEVEL=medium pnpm start   # extended reasoning on every turn
THINKING_LEVEL=off pnpm start      # default — no reasoning, fastest
```

Valid values: `off` | `minimal` | `low` | `medium` | `high` | `xhigh`

> **Note**: `xhigh` is only supported by OpenAI GPT-5.2/5.3 and Anthropic Opus 4.6 (where it maps to adaptive effort "max"). Setting it on an unsupported model will produce an error. Use `medium` or `high` as safe upgrades for Claude Haiku/Sonnet.

**Files changed:**

* `packages/shared/src/config/index.ts` — added `thinkingLevel` Zod enum field
* `packages/core/src/agent/index.ts` — `thinkingLevel?` in `AgentDeps`; passed to `Agent` constructor; logged at startup
* `src/index.ts` — passes `config.thinkingLevel` into `AgentDeps`

***

## February 19, 2026 — LLM Payload Debug Logging

### New env var: `LOG_LLM_PAYLOADS`

Set `LOG_LLM_PAYLOADS=true` to log the raw request payload sent to the LLM provider before each API call. Logged at Pino `debug` level under the key `payload`.

```bash theme={null}
LOG_LLM_PAYLOADS=true pnpm start
```

**Files changed:**

* `packages/shared/src/config/index.ts` — added `logLlmPayloads` field
* `packages/core/src/agent/index.ts` — wraps `agent.streamFn` with `onPayload` hook when enabled
* `src/index.ts` — passes `config.logLlmPayloads` into `AgentDeps`

**Security note:** Pino redaction (`ANTHROPIC_API_KEY`, `Authorization` headers) applies before any log output, so keys are not exposed even with this flag enabled. Do not enable in production unless actively debugging.

***

## February 18, 2026 — Distribution & First-Run Setup Wizard

### Setup Wizard (`pnpm wizard` / `pnpm wizard:cli`)

A new interactive setup wizard (`scripts/setup.ts`) replaces the manual `.env` editing workflow.

**Usage:**

```bash theme={null}
pnpm wizard                            # browser-based wizard (recommended)
pnpm wizard:cli                        # terminal wizard
pnpm wizard:check                      # prerequisite check only
pnpm wizard:cli --non-interactive      # CI/Ansible mode (reads env vars, writes .env)
pnpm wizard:cli --skip-validation      # skip live API key checks
```

**What it does:**

1. Checks Node 20+, pnpm 10+, disk space
2. Detects existing `.env` — offers update / replace / skip
3. Collects and validates Anthropic key (required), OpenAI key (optional), Telegram token
4. Configures interfaces (Telegram, Web UI) and ports
5. Configures Redis scheduler with cron schedules
6. Shows masked summary before writing
7. Writes `.env` (mode 0600), backs up old `.env` as `.env.backup.{timestamp}`
8. Creates data directories
9. Offers to run `pnpm build` if no dist found

**Security properties:**

* All keys entered via `password()` (masked `*`) — never visible in terminal
* Keys are NOT accepted as CLI arguments (would appear in `ps aux`)
* `.env` written with `chmod 0600` immediately
* Env file parsed with simple line-by-line reader — no `eval()`, no shell interpolation
* API validation uses `fetch()` with `AbortSignal.timeout(10000)` — keys never logged

**Non-interactive mode (CI/Ansible):**

```bash theme={null}
ANTHROPIC_API_KEY=sk-ant-... \
ALLOWED_USER_IDS=123456789 \
ENABLE_TELEGRAM=false \
ENABLE_WEB=true \
pnpm wizard:cli --non-interactive --skip-validation
```

### Config schema fix — `telegramBotToken` now optional

`packages/shared/src/config/index.ts`: `telegramBotToken` changed from `z.string().min(1)` to `z.string().optional()`.

**Why**: Web-only deployments were blocked by a required `TELEGRAM_BOT_TOKEN` even when `ENABLE_TELEGRAM=false`. The token is still validated at runtime in `src/index.ts` before the Telegram adapter is created.

### First-run detection in `src/index.ts`

`src/index.ts` now exits with a helpful message if `.env` is missing:

```
No .env file found. Run: pnpm wizard (or pnpm wizard:cli)
```

Previously, missing config produced a cascade of confusing Zod validation errors.

### Docker improvements

* `depends_on.redis.required: false` — EchOS starts without Redis when scheduler is disabled
* Healthcheck added to `echos` service (HTTP GET `/health`)
* `nginx` and `certbot` services added under `--profile nginx`
* `docker/nginx.conf.template` created with SSE-compatible proxy config and Let's Encrypt instructions

### install.sh (VPS one-liner)

```bash theme={null}
curl -sSL https://raw.githubusercontent.com/albinotonnina/echos/main/install.sh | bash
```

Detects platform, checks/installs prerequisites (Node 20+, pnpm, git), clones repo, installs deps, launches wizard. Falls back gracefully when no TTY is available (piped curl).

***

## February 15, 2026 - Initial Setup Fixes

### Issues Fixed

1. **Workspace Package Resolution**
   * **Problem**: tsx couldn't resolve `@echos/*` workspace packages when running `src/index.ts`
   * **Solution**: Added TypeScript path mappings to root `tsconfig.json`
   * **Files changed**: `tsconfig.json`
   * **Details**: Added `paths` configuration mapping all `@echos/*` packages to their source locations

2. **LanceDB Native Module Compatibility**
   * **Problem**: LanceDB 0.26.2 dropped support for Intel Macs (darwin-x64)
   * **Solution**: Downgraded to LanceDB 0.22.3
   * **Files changed**: `packages/core/package.json`
   * **Details**: Changed `"@lancedb/lancedb": "^0.26.2"` to `"^0.22.3"`

3. **Environment File Loading**
   * **Problem**: Environment variables weren't being loaded from `.env` file
   * **Solution**: Added `--env-file` flag to start script (Node 20.6+ feature)
   * **Files changed**: `package.json`
   * **Details**: Changed `"start": "tsx src/index.ts"` to `"tsx --env-file=.env src/index.ts"`

### Configuration Changes

#### tsconfig.json (Root)

Added path mappings for workspace packages:

```json theme={null}
{
  "compilerOptions": {
    // ... existing config
    "paths": {
      "@echos/shared": ["./packages/shared/src/index.ts"],
      "@echos/shared/*": ["./packages/shared/src/*"],
      "@echos/core": ["./packages/core/src/index.ts"],
      "@echos/core/*": ["./packages/core/src/*"],
      "@echos/telegram": ["./packages/telegram/src/index.ts"],
      "@echos/web": ["./packages/web/src/index.ts"],
      "@echos/cli": ["./packages/cli/src/index.ts"],
      "@echos/scheduler": ["./packages/scheduler/src/index.ts"]
    }
  }
}
```

**Why**: tsx needs explicit path mappings to resolve pnpm workspace packages when running TypeScript files directly.

#### packages/core/package.json

Changed LanceDB version:

```json theme={null}
{
  "dependencies": {
    "@lancedb/lancedb": "^0.22.3"  // was "^0.26.2"
  }
}
```

**Why**: LanceDB 0.26.2 removed darwin-x64 (Intel Mac) native bindings. Version 0.22.3 is the latest with full macOS support.

#### package.json (Root)

Updated start script:

```json theme={null}
{
  "scripts": {
    "start": "tsx --env-file=.env src/index.ts"  // was "tsx src/index.ts"
  }
}
```

**Why**: Node.js 20.6+ supports native `.env` file loading via the `--env-file` flag, eliminating the need for dotenv packages.

### Build Process

The correct startup sequence is:

1. **Install dependencies**: `pnpm install`
2. **Build all packages**: `pnpm build` (required on first run and after any package changes)
3. **Configure environment**: Edit `.env` file with API keys
4. **Start application**: `pnpm start`

### Known Issues

#### Telegram Bot Conflicts

**Symptom**: `GrammyError: Call to 'getUpdates' failed! (409: Conflict)`

**Cause**: Another instance of the bot is already running. Telegram allows only one instance to poll for updates.

**Fix**:

```bash theme={null}
pkill -f "tsx.*index.ts"
pnpm start
```

**Prevention**: For production, use webhooks instead of polling.

### Documentation Updates

* `README.md`: Added first-time setup notes and process management instructions
* `docs/DEPLOYMENT.md`: Added troubleshooting section and process management guide
* `docs/TROUBLESHOOTING.md`: New comprehensive troubleshooting guide
* `docs/SETUP_FIXES.md`: This file

### Environment Variables

Required variables (must be set in `.env`):

* `TELEGRAM_BOT_TOKEN` - From @BotFather
* `ALLOWED_USER_IDS` - Comma-separated Telegram user IDs
* **At least one LLM key:** `ANTHROPIC_API_KEY` (Anthropic) or `LLM_API_KEY` (other providers)

Optional:

* `LLM_BASE_URL` - Custom OpenAI-compatible endpoint; requires `LLM_API_KEY`
* `OPENAI_API_KEY` - For embeddings and Whisper
* `WHISPER_LANGUAGE` - ISO-639-1 code (e.g. `en`) to pin Whisper language detection

See `.env.example` for full list with defaults.

### Verification Steps

To verify the setup is working:

1. **Check Node version**: `node --version` (should be 20+)
2. **Check pnpm version**: `pnpm --version` (should be 9+)
3. **Check packages built**: `ls packages/*/dist` (should show compiled JS files)
4. **Check Redis running**: `redis-cli ping` (should return PONG)
5. **Check env file**: `head .env` (should show configured values, without secrets!)
6. **Start application**: `pnpm start` (should start without errors)

### Available Commands

* `pnpm start` - Start daemon (Telegram + Web if enabled)
* `pnpm start:web-only` - Start only the Web API (port 3000)
* `pnpm echos` - Standalone CLI (interactive REPL, no daemon needed)
* `pnpm echos "query"` - One-shot CLI query
* `pnpm dev` - Watch mode for development (rebuilds on changes)
* `pnpm test` - Run all tests
* `pnpm build` - Build all workspace packages

See [docs/INTERFACES.md](/interfaces) for interface usage details.

### Platform-Specific Notes

#### macOS (Intel)

* Uses LanceDB 0.22.3 with darwin-x64 native bindings
* Run `pnpm install --force` if native module errors occur

#### macOS (Apple Silicon)

* Could use newer LanceDB versions if needed
* Current version (0.22.3) works on both architectures

#### Linux

* Should work with LanceDB 0.22.3 or newer
* Native bindings auto-detected by platform

#### Windows

* Not extensively tested but should work
* May need WSL for better compatibility

### Future Improvements

* [ ] Consider using PM2 or similar for process management
* [ ] Add webhook support for Telegram (production recommended)
* [ ] Create systemd service file for Linux deployments
* [ ] Add health check endpoint for monitoring
* [ ] Document upgrade path for LanceDB when arm64 stability improves
