openclaw.json now sends `Authorization: Bearer ${AGAP_MCP_TOKEN}` to the agap
MCP server, which requires it as of kb#180. The token is injected from
openai/.env via docker-compose.yml and only substituted here, never inlined.
It maps to agent id `adolf`, which is also what the kb#147 vault gate reads.
Fixes tools.media.audio, which had been added but never restart-validated:
the per-entry `apiKey: "not-needed"` is rejected by the schema
("tools.media.audio.models.0: Invalid input"), and an invalid config makes the
gateway refuse to start outright -- adolf crash-looped on the first restart
after the block landed. The old comment claimed the schema requires a
non-empty apiKey; it is the opposite, apiKey is not a valid per-entry key at
all. Isolated with `openclaw config validate` against the running image
(2026.6.11): {provider, model} and {provider, model, baseUrl} validate, and
adding apiKey alone reproduces the failure. baseUrl is kept -- that is the
per-entry override pointing the openai-shaped provider at the local
faster-whisper server. Provider auth follows the normal model auth order per
docs/nodes/audio.md, and faster-whisper-server has no auth to satisfy anyway.
Two lessons encoded in the comments: `enabled: false` does NOT exempt an entry
from schema validation, and a config edit is not done until a restart boots
healthy -- this sat invalid but latent because the running gateway still held
an older loaded config. The block stays enabled: false; turning STT on is
still a kb#175/#191 decision (GTX 1070 co-residency).
Also adds the proactive-prioritization and todoist-capture design notes and
the vw-mcp prototype.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
6.5 KiB
Markdown
151 lines
6.5 KiB
Markdown
# Adolf — OpenClaw gateway deployment
|
||
|
||
Adolf is the self-hosted OpenClaw fork that runs as the `adolf` container
|
||
(Matrix-first personal assistant). This directory holds its **version-controlled
|
||
gateway configuration**.
|
||
|
||
- Source tree (the OpenClaw fork being built): `/home/alvis/adolf`
|
||
- Compose service `adolf` lives in: `openai/docker-compose.yml`
|
||
- This config directory lives at the repo root (`agap_git/adolf/`), not
|
||
nested inside `openai/`, since it is shared config rather than part of
|
||
the `openai` compose project's own tree.
|
||
- Model backend: `adolf-llm` container (Kimi-CLI wrapper) on `:8010`
|
||
|
||
## Memory
|
||
|
||
Adolf's long-term memory is being **migrated from Cognee to Hindsight** (a single
|
||
self-hosted container, `:8888` REST + built-in MCP, `:9999` UI). It stays wired in
|
||
the same two ways: as a **tool** (Hindsight's built-in MCP in `openclaw.json`
|
||
`mcp.servers.hindsight`) and as **forced hooks** (the `hindsight-memory` OpenClaw
|
||
plugin: `before_prompt_build`→recall inject, `agent_end`→retain). Authoritative
|
||
plan and target architecture: **[`HINDSIGHT-MIGRATION.md`](./HINDSIGHT-MIGRATION.md)**
|
||
(kanboard *Adolf* tasks H1–H5). Until those land, the running stack is still Cognee.
|
||
|
||
## Config source of truth
|
||
|
||
The gateway config is **`openclaw.json` in this directory**. It is bind-mounted
|
||
**read-only** over the `adolf-state` volume:
|
||
|
||
```yaml
|
||
volumes:
|
||
- adolf-state:/home/node/.openclaw # runtime state only
|
||
- ../adolf/openclaw.json:/home/node/.openclaw/openclaw.json:ro # tracked config
|
||
```
|
||
|
||
Previously this file was a hand-edited copy inside the `adolf-state` Docker
|
||
volume (edited via `docker cp` into the running container). It is now tracked
|
||
in git and seeded into the container by the mount, so **git is the single
|
||
source of truth**.
|
||
|
||
- The file is **JSONC** (comments + unquoted keys allowed).
|
||
- **No secrets live here.** Every credential is a `${VAR}` reference resolved
|
||
from the container's environment, which is sourced from `openai/.env`
|
||
(gitignored, never committed): `OPENCLAW_GATEWAY_TOKEN`, `ADOLF_KEY`,
|
||
`MATRIX_*`, `MARKETPLACE_MCP_TOKEN`.
|
||
- The gateway reads this file and writes its own `openclaw.json.last-good`
|
||
and `openclaw.json.rejected.*` snapshots into the volume dir (writable). It
|
||
does **not** rewrite this file, so the read-only mount is safe.
|
||
|
||
### Changing the config
|
||
|
||
1. Edit `agap_git/adolf/openclaw.json`.
|
||
2. Restart the container:
|
||
```bash
|
||
env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \
|
||
docker compose -f /home/alvis/agap_git/openai/docker-compose.yml up -d adolf
|
||
```
|
||
3. Verify it came up healthy and the config was accepted:
|
||
```bash
|
||
env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \
|
||
docker ps --filter name=adolf --format '{{.Names}} {{.Status}}'
|
||
env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \
|
||
docker logs --tail 40 adolf
|
||
```
|
||
A fresh `openclaw.json.rejected.*` file in `/home/node/.openclaw` means the
|
||
edit failed validation and the previous `.last-good` is still in use.
|
||
|
||
Because the mount is read-only, editing config through the gateway UI/API is
|
||
intentionally disabled — all changes go through git.
|
||
|
||
## Granting a Matrix user access to Adolf
|
||
|
||
Adolf only responds to Matrix users on an **allow-list**. This is the
|
||
`channels.matrix.dm` block in `openclaw.json`:
|
||
|
||
```jsonc
|
||
channels: {
|
||
matrix: {
|
||
enabled: true,
|
||
encryption: true,
|
||
dm: {
|
||
policy: "allowlist",
|
||
allowFrom: [
|
||
"@admin:mtx.alogins.net",
|
||
"@elizaveta:mtx.alogins.net",
|
||
],
|
||
},
|
||
groupPolicy: "disabled", // no group-room handling yet
|
||
autoJoin: "always",
|
||
},
|
||
}
|
||
```
|
||
|
||
- **`dm.policy: "allowlist"`** — only users whose full Matrix ID appears in
|
||
`allowFrom` can DM the bot. Everyone else is ignored.
|
||
(`policy: "pairing"` is the alternative: the owner must approve each unknown
|
||
sender interactively. `allowlist` is stricter and declarative.)
|
||
- **`dm.allowFrom`** — the list of authorized Matrix user IDs.
|
||
- **`groupPolicy: "disabled"`** — Adolf does not act in group rooms; DM only.
|
||
|
||
### To grant a new user access
|
||
|
||
1. Get the user's **full Matrix ID**, e.g. `@ivan:mtx.alogins.net`.
|
||
2. Add it to `allowFrom` in `agap_git/adolf/openclaw.json`:
|
||
```jsonc
|
||
allowFrom: [
|
||
"@admin:mtx.alogins.net",
|
||
"@elizaveta:mtx.alogins.net",
|
||
"@ivan:mtx.alogins.net",
|
||
],
|
||
```
|
||
3. Restart adolf (see "Changing the config" above).
|
||
4. The user can now start a DM with Adolf's Matrix account
|
||
(`MATRIX_USER_ID` in `openai/.env`). `autoJoin: "always"` means Adolf
|
||
auto-accepts the DM invite; conversation is end-to-end encrypted
|
||
(`encryption: true`).
|
||
|
||
To **revoke** access, remove the ID from `allowFrom` and restart.
|
||
|
||
> Matrix accounts themselves are created on the Synapse homeserver
|
||
> (`mtx.alogins.net`) — see the AgapHost wiki **Matrix** page. The allow-list
|
||
> here only controls which existing Matrix users Adolf will talk to.
|
||
|
||
## Matrix device identity (kb#67)
|
||
|
||
Adolf's Matrix login used **password auth with no pinned `device_id`**
|
||
(`MATRIX_PASSWORD` in `openai/.env`). Every time OpenClaw's own credential
|
||
cache (in the `adolf-state` volume) was missing — first boot, a lost/rebuilt
|
||
volume — a fresh password login minted a **brand-new Matrix device** with no
|
||
cross-signing, leaving dead ghost devices behind and risking new encrypted
|
||
DMs getting keys shared to a device that no longer exists.
|
||
|
||
Fix: `openai/.env` now also pins `MATRIX_ACCESS_TOKEN` + `MATRIX_DEVICE_ID` to
|
||
Adolf's current live device (`TIANDTKUZJ`, cross-signed; token in Vaultwarden
|
||
as `MATRIX_ADOLF_GATEWAY_TOKEN`). OpenClaw's matrix extension prefers a
|
||
configured access token over password login
|
||
(`extensions/matrix/src/matrix/client/config.ts` `resolveMatrixAuth`), so as
|
||
long as that token stays valid, restarts — even after a volume loss — reuse
|
||
the same device instead of minting a new one. `MATRIX_PASSWORD` stays set as
|
||
a manual-recovery fallback only (unset `MATRIX_ACCESS_TOKEN` to force a fresh
|
||
password login if the token is ever revoked).
|
||
|
||
Cross-signing for `@bot` is already bootstrapped automatically by OpenClaw's
|
||
matrix extension (`extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts`) —
|
||
no separate setup needed.
|
||
|
||
If the pinned token is ever revoked/rotated, get a fresh one bound to the
|
||
*same* device by logging in with `device_id` explicitly set to `TIANDTKUZJ`
|
||
(Synapse reuses an existing device when its id is given in `/login`, instead
|
||
of creating a new one), then update `MATRIX_ACCESS_TOKEN` in `openai/.env`
|
||
and Vaultwarden's `MATRIX_ADOLF_GATEWAY_TOKEN`.
|