# ethos agent type

TypeScript AI agent framework with personality-driven architecture. Managed by clawrium as a first-class agent type alongside hermes, zeroclaw, and openclaw.

## Directory layout

```
ethos/
├── manifest.yaml                # Agent type declaration, features, secrets, onboarding
├── playbooks/
│   ├── install.yaml             # Phase 1: install binary + create agent user + drop disabled unit
│   ├── configure.yaml           # Phase 2: render .env + config.yaml + enable+start unit
│   ├── start.yaml               # Start (and re-render unit for upgrade safety)
│   ├── stop.yaml                # Stop + disable unit; preserves ~/.ethos/
│   ├── remove.yaml              # Full teardown: unit, ~/.ethos/, npm pkg, agent user
│   ├── exec.yaml                # Passthrough: run ethos CLI as agent user
│   ├── skills_apply.yaml        # Reconcile ~/.ethos/skills/clawrium/ with desired state
│   ├── memory_info.yaml         # Stat MEMORY.md / USER.md / SOUL.md
│   ├── memory_read.yaml         # Read a memory file
│   ├── memory_write.yaml        # Overwrite a memory file
│   └── memory_delete.yaml       # Delete a memory file
└── templates/
    ├── ethos.env.j2             # ~/.ethos/.env (provider key + gateway key + channels)
    ├── ethos-config.yaml.j2     # ~/.ethos/config.yaml (provider, model, personality)
    ├── ethos-soul.md.j2         # ~/.ethos/personalities/default/SOUL.md
    ├── ethos-personality-config.yaml.j2  # personality config (memory.scope, autoLoad)
    ├── ethos-toolset.yaml.j2    # personality toolset (tools list, fs_reach, safety)
    └── verify_ethos.js          # Node.js smoke-test run during configure
```

## How it works

### Install phase (`clawctl agent install`)

1. Creates a dedicated Linux user `<agent_name>` with shell `/usr/sbin/nologin` (service account, no interactive login).
2. Installs Node.js 24 via NodeSource if the current Node major version is < 24.
3. Installs `@ethosagent/cli` globally via npm (version from `manifest.yaml`).
4. Creates `~/.ethos/` directory tree and a placeholder `.env` / `config.yaml`.
5. Drops the systemd unit `ethos-<agent_name>.service` **disabled and stopped** — the unit gets a live provider key only after configure.
6. Clawrium generates a 64-char hex `ETHOS_GATEWAY_API_KEY` at install time and persists it to `hosts.json.agents.<name>.config.gateway.api_key` and to the secrets store.

### Configure phase (`clawctl agent configure`)

1. Verifies `ETHOS_GATEWAY_API_KEY` is present in the agent's config (generated at install).
2. Renders `~/.ethos/.env` from `ethos.env.j2` (provider API key + gateway key + optional channel tokens).
3. Renders `~/.ethos/config.yaml` from `ethos-config.yaml.j2` (provider type, model, personality pointer).
4. Renders personality files: `SOUL.md`, `toolset.yaml`, `config.yaml` under `~/.ethos/personalities/default/`.
5. Renders git integrations (`~/.gitconfig`, `gh auth login`) if integrations are configured.
6. Restarts the systemd unit via the `Restart ethos service` handler.
7. After the playbook exits, clawrium runs `ethos api-key create --name clawctl` as `<agent_name>` over SSH to mint the `ETHOS_CHAT_TOKEN` (`sk-ethos-...`) and stores it in secrets.

### Start / Stop

`start.yaml` re-renders the systemd unit on every invocation so binary path changes after a Node.js upgrade are picked up without a full reinstall. It then checks `ActiveState` and fails loudly if the unit doesn't reach `active`.

`stop.yaml` stops and disables the unit, then asserts the process is gone via `pgrep`. **`~/.ethos/` is preserved** (credentials, memory, personality files survive a stop/start cycle).

### Remove

`remove.yaml` tears down everything:

1. Stop + disable the systemd unit.
2. Remove `~/.ethos/` (all config and data).
3. Uninstall `@ethosagent/cli` globally via npm (guarded: skipped when other ethos agent users exist on the same host, since the binary is shared).
4. Kill any residual processes (`pkill -KILL -u <agent_name>`).
5. Delete the Linux user and home directory.

## Token architecture

Three distinct credentials flow through the ethos stack:

| Token | Name | Where stored | Purpose |
|---|---|---|---|
| `ETHOS_GATEWAY_API_KEY` | 64-char hex | `hosts.json` config + secrets store | Internal gateway auth (bearer for `/rpc/*`) |
| `ETHOS_CHAT_TOKEN` | `sk-ethos-<64hex>` | secrets store | `clawctl agent chat` — OpenAI `/v1/` API bearer |
| `web-token` | raw hex file | `~/.ethos/web-token` on remote host | Dashboard login (`/auth/exchange?t=TOKEN`) |

`ETHOS_GATEWAY_API_KEY` is generated once at install and written to `.env` by configure. It does not rotate.

`ETHOS_CHAT_TOKEN` is created post-configure by clawrium via SSH (`ethos api-key create --name clawctl`). Unlike zeroclaw's gateway bearer it does not rotate on restart.

`web-token` is generated by ethos at runtime and lives only on the remote host. `clawctl agent open` reads it on-demand over SSH and appends it to the dashboard URL.

## Port architecture

| Port | Binding | Config field | Purpose |
|---|---|---|---|
| 3000 | `127.0.0.1` | `config.gateway.port` | Web API — `/v1/*` (OpenAI compat), `/rpc/*`, dashboard SPA, `/healthz` |
| 3001 | `127.0.0.1` | — | ACP WebSocket (internal agent communication bus) |
| per-instance (43000–44999) | `127.0.0.1` | `config.gateway.internal_port` | Internal ethos gateway (`ETHOS_GATEWAY_PORT`) |

`config.gateway.port = 3000` is persisted at install time. The `features.web_ui.port_field: gateway.port` resolver reads this field so both `clawctl agent open` and `clawctl agent chat` tunnel to port 3000.

`config.gateway.internal_port` (43000–44999) is per-instance and rendered as `ETHOS_GATEWAY_PORT` in `.env`. Clawrium does not connect to this port directly.

The health check after `clawctl agent start` probes `http://127.0.0.1:3000/healthz` over SSH.

## Memory layout

Ethos memory is stored as markdown files under the default personality directory:

```
~/.ethos/personalities/default/
├── SOUL.md      # Agent identity (≤ 3000 chars, rendered from ethos-soul.md.j2)
├── MEMORY.md    # Agent notes (written by ethos at runtime)
└── USER.md      # User profile (written by ethos at runtime)
```

`clawctl agent memory show/edit/delete` dispatches to `memory_*.yaml` playbooks against this path.

## Skills

Skills are materialized under `~/.ethos/skills/clawrium/` (one directory per skill, with a `SKILL.md` inside). Only the `clawrium/` namespace is managed; other skill namespaces are never touched. The `skills_apply.yaml` playbook reconciles desired state idempotently: installs new skills, prunes removed skills.

## Service user model

The Linux user created at install (`agent_name`) is the service user. `ETHOS_STATE_DIR` is set to `/home/<agent_name>/.ethos` in the systemd unit so ethos reads its data from there regardless of `$HOME`. This means agent names are also Linux usernames — they must satisfy `^[a-z][a-z0-9_-]{0,31}$`.

## Supported platforms

| OS | Version | Arch | Node.js req. | Min RAM |
|---|---|---|---|---|
| Ubuntu | 22.04, 24.04, 26.04 | x86_64 | ≥ 24.0.0 | 2 GB |

---

## Agent type comparison

| Feature | ethos | hermes | zeroclaw | openclaw |
|---|---|---|---|---|
| **Language** | TypeScript (Node.js) | Python | Rust (statically linked) | Node.js |
| **Install method** | npm global (`@ethosagent/cli`) | bash install script | prebuilt binary (sha256 verified) | npm package |
| **Systemd units** | 1 (gateway + web-ui in one process) | 2 (gateway + dashboard) | 1 | 1 |
| **Service user** | dedicated Linux user = `agent_name` | dedicated Linux user | dedicated Linux user | dedicated Linux user |
| **Chat protocol** | OpenAI HTTP (`/v1/chat/completions`) | OpenAI HTTP (`/v1/chat/completions`) | ZeroClaw WebSocket (tagged JSON) | WebSocket (openclaw envelope) |
| **Web UI** | built-in (same port 3000) | separate dashboard unit (port 45000–46999) | built-in (same port as gateway) | none |
| **Web UI bind** | `127.0.0.1` (loopback) | `127.0.0.1` (loopback) | `0.0.0.0` (wildcard) | — |
| **Web UI auth** | `web-token` file (`/auth/exchange?t=`) | none (SSH key is auth boundary) | none (SSH key is auth boundary) | — |
| **Port range** | 3000 (web-api, fixed upstream) | 45000–46999 (dashboard, per-instance) | 40000–41999 (gateway, per-instance) | varies |
| **Gateway auth token** | `ETHOS_GATEWAY_API_KEY` (static 64-char hex) | `API_SERVER_KEY` (static) | bearer rotated on configure/restart | none |
| **Chat token** | `ETHOS_CHAT_TOKEN` (`sk-ethos-...`, separate) | same as gateway key | gateway bearer | WebSocket session |
| **Token rotation** | no rotation | no rotation | rotates on configure/restart | — |
| **Providers** | OpenRouter, Anthropic, OpenAI | OpenRouter, Anthropic, OpenAI | any OpenAI-compat URL | OpenAI-compat |
| **Channels** | CLI, Discord, Slack, Telegram | CLI, Discord, Slack | CLI, Discord | CLI, web, WhatsApp, Slack, Discord, Telegram |
| **Memory** | `~/.ethos/personalities/default/` | `~/.hermes/memories/` + `~/.hermes/SOUL.md` | `~/.zeroclaw/workspace/` | `~/.openclaw/workspace/memory/` |
| **Skills** | `~/.ethos/skills/clawrium/` | `~/.hermes/skills/clawrium/` | `~/.zeroclaw/workspace/skills/` | `~/.openclaw/skills/` |
| **Personality config** | `SOUL.md` + `toolset.yaml` + `config.yaml` (per-personality dir) | managed by hermes upstream | 7 workspace MD files | `SOUL.md` + `IDENTITY.md` |
| **restart.yaml** | no (not needed — no bearer rotation) | no | yes (re-pairs gateway bearer) | no |
| **linger (remove)** | not needed | `loginctl disable-linger` on remove | not needed | not needed |
| **Min RAM** | 2 GB | 2 GB | 512 MB (Pi 2) / 1 GB (Pi 4+) | 2 GB |
| **ARM support** | no | no | yes (armv7l, aarch64) | no |
| **Declarative apply** | yes (`clawctl apply -f fleet.yaml`) | yes | yes | yes |
| **Health check** | SSH → `curl 127.0.0.1:3000/healthz` | systemd `ActiveState` | systemd `ActiveState` | systemd `ActiveState` |
