adolf: bearer-authenticate the agap MCP server, fix audio config schema
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>
This commit is contained in:
@@ -29,6 +29,71 @@
|
||||
},
|
||||
},
|
||||
|
||||
// Audio understanding (kb#175, STT source 2: "local STT on Agap").
|
||||
// OpenClaw's bundled media-understanding pipeline (docs/nodes/media-
|
||||
// understanding.md) auto-transcribes inbound audio attachments before the
|
||||
// agent turn runs -- no plugin code needed, this is pure config. A voice
|
||||
// note sent to Adolf over the existing Matrix DM (source 1, see below)
|
||||
// gets transcribed by the `openai`-shaped entry below, which is redirected
|
||||
// via baseUrl/apiKey to the LOCAL faster-whisper server (openai/docker-
|
||||
// compose.yml's `faster-whisper` service, same compose project as this
|
||||
// container, reachable by service name) instead of hosted OpenAI --
|
||||
// confirmed supported via src/media-understanding/runner.entries.ts's
|
||||
// per-entry baseUrl override (docs/gateway/config-tools.md "Tools and
|
||||
// custom providers"). apiKey is a dummy: faster-whisper-server has no
|
||||
// auth, but OpenClaw's schema requires a non-empty value.
|
||||
//
|
||||
// ⚠️ NOT ACTIVATED YET (2026-07-26): the `faster-whisper` container does
|
||||
// not exist (never started -- `docker ps -a` shows no such container).
|
||||
// Starting it opens a 4th tenant on the single 8GB GTX 1070 already at
|
||||
// ~6.2GB with bge-m3 + gemma3:4b + tei-reranker (~1.8GB headroom) and
|
||||
// could evict tei-reranker, silently breaking Hindsight recall (see
|
||||
// DESIGN-a2a-agents.md §3b and kb#191, which is the unimplemented
|
||||
// residency-guard/VRAM-alert task -- still in Backlog). This config
|
||||
// block is deliberately inert until #191 lands or alvis explicitly
|
||||
// accepts the co-residency risk; `enabled: false` below.
|
||||
// SCHEMA FIX 2026-07-30 (kb#180 restart). This block previously carried a
|
||||
// per-entry `apiKey: "not-needed"`, which the schema REJECTS:
|
||||
// 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 was added. Two lessons
|
||||
// encoded here: `enabled: false` does NOT exempt an entry from schema
|
||||
// validation, and a config edit is not "done" until a real restart boots
|
||||
// healthy (this block sat invalid but latent because the running gateway
|
||||
// still held an older loaded config).
|
||||
//
|
||||
// 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. Verified with
|
||||
// `openclaw config validate` against this image (2026.6.11): entries with
|
||||
// {provider, model} and {provider, model, baseUrl} validate; adding
|
||||
// `apiKey` is the sole cause of the failure. Per docs/nodes/audio.md,
|
||||
// provider auth follows the normal model auth order (auth profiles, env
|
||||
// vars, models.providers.*.apiKey) -- and faster-whisper-server has no auth
|
||||
// to satisfy anyway, so no key belongs here. `baseUrl` is kept: that is the
|
||||
// per-entry override that redirects the `openai`-shaped provider to the
|
||||
// LOCAL faster-whisper server.
|
||||
tools: {
|
||||
media: {
|
||||
audio: {
|
||||
// Still deliberately inert: the `faster-whisper` container does not
|
||||
// exist, and starting it opens a 4th tenant on the 8GB GTX 1070 (see
|
||||
// the kb#191 residency-guard note above). This block is now merely
|
||||
// SCHEMA-VALID rather than boot-breaking; flipping this to true is a
|
||||
// separate decision that still belongs to kb#175/#191.
|
||||
enabled: false,
|
||||
echoTranscript: true, // let the sender see what Adolf heard before it acts
|
||||
echoFormat: '📝 "{transcript}"',
|
||||
models: [
|
||||
{
|
||||
provider: "openai",
|
||||
model: "deepdml/faster-whisper-large-v3-turbo-ct2", // must match WHISPER__MODEL in openai/docker-compose.yml
|
||||
baseUrl: "http://faster-whisper:8000/v1",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Browser tool — bundled plugin, off by default. Enables a dedicated,
|
||||
// agent-only headless Chromium profile ("openclaw") driven through the
|
||||
// gateway's loopback control service. Chromium is already in the image
|
||||
@@ -146,9 +211,27 @@
|
||||
// of which Adolf's Matrix persona drives turn-to-turn; reach the
|
||||
// hindsight MCP directly (unscoped) for that admin work instead of
|
||||
// paying for it on every Adolf turn. 29 tools -> 9.
|
||||
//
|
||||
// kb#169: this raw MCP surface previously pointed at /mcp/adolf/ --
|
||||
// the single unpartitioned bank with content from every human's
|
||||
// conversations. #153 scopes the hindsight-memory PLUGIN's
|
||||
// recall/retain hooks by interlocutor, but this MCP tool surface is a
|
||||
// second, independent path to memory that #153 does not touch: a
|
||||
// model call to e.g. `recall` here bypassed interlocutor scoping
|
||||
// entirely. Repointed to /mcp/adolf-shared/ (option 2 of #169) --
|
||||
// the household-shared bank (0 facts as of 2026-07-26, pre-existing
|
||||
// per agent-registry.yaml's memory.banks target list). This surface
|
||||
// can now only ever read/write the shared bank, never a private one,
|
||||
// regardless of which human is talking to Adolf -- safe by
|
||||
// construction, no dependency on #153 landing first. Option 1 (drop
|
||||
// entirely) was not chosen because, until #153's scoped plugin tools
|
||||
// land, this is still Adolf's only path for explicit "remember
|
||||
// this"/"what do you recall" turns; option 3 (dynamic per-interlocutor
|
||||
// bank selection) is not supported -- this config's url is a single
|
||||
// static bank_id per MCP server entry, not a per-request parameter.
|
||||
hindsight: {
|
||||
type: "http",
|
||||
url: "http://hindsight:8888/mcp/adolf/",
|
||||
url: "http://hindsight:8888/mcp/adolf-shared/",
|
||||
toolFilter: {
|
||||
include: ["recall", "retain", "reflect", "list_memories", "get_memory", "update_memory", "list_directives", "create_directive", "delete_directive"],
|
||||
},
|
||||
@@ -210,7 +293,7 @@
|
||||
},
|
||||
},
|
||||
// agap-mcp (kb#64) -- the SAME shared agap-mcp instance Claude Code uses
|
||||
// (network_mode: host, :3100, unauthenticated on localhost). Grants Adolf
|
||||
// (network_mode: host, :3100, bearer-authenticated since kb#180). Grants Adolf
|
||||
// the same access as Claude: vault (vw_* for credential fetching) plus
|
||||
// gitea/ha/zabbix/radicale. Reached via host.docker.internal like
|
||||
// kanboard/marketplace above.
|
||||
@@ -228,8 +311,24 @@
|
||||
agap: {
|
||||
type: "http",
|
||||
url: "http://host.docker.internal:3100/mcp",
|
||||
// kb#180: agap-mcp's listener is authenticated now (DESIGN §4 --
|
||||
// :3100 is host-networked and the LAN carries VPN peers, so an
|
||||
// open JSON-RPC listener handed ha_call_service/wiki_edit/todoist
|
||||
// writes to anyone). Same pattern as marketplace above:
|
||||
// AGAP_MCP_TOKEN lives in Vaultwarden, is injected into this
|
||||
// container via openai/.env -> docker-compose.yml, and is only
|
||||
// substituted here -- never inlined. The token maps to agent id
|
||||
// `adolf` in AGAP_MCP_AGENT_TOKENS, which is also what the kb#147
|
||||
// vault gate reads to allow vw_* (adolf = trust_class trusted).
|
||||
headers: {
|
||||
Authorization: "Bearer ${AGAP_MCP_TOKEN}",
|
||||
},
|
||||
toolFilter: {
|
||||
include: ["vw_get_password", "vw_get_item", "vw_list_items", "vw_create_login", "vw_update_password", "ha_get_state", "ha_list_entities", "ha_call_service", "ha_get_history", "zabbix_get_problems", "zabbix_get_hosts", "zabbix_get_items", "zabbix_get_triggers", "radicale_list_calendars", "radicale_list_events", "radicale_get_event", "radicale_put_event", "radicale_delete_event", "radicale_move_event", "todoist_list_tasks", "todoist_list_projects", "todoist_create_task", "todoist_update_task", "todoist_complete_task", "wiki_search", "wiki_read", "wiki_edit"],
|
||||
// kb#170: added todoist_capture_idea (classify + create in one
|
||||
// call — see agap-mcp/src/capture.js) so Adolf's proactive-
|
||||
// secretary persona can capture+tag an idea in one tool call
|
||||
// instead of list_projects+create_task+manual tagging.
|
||||
include: ["vw_get_password", "vw_get_item", "vw_list_items", "vw_create_login", "vw_update_password", "ha_get_state", "ha_list_entities", "ha_call_service", "ha_get_history", "zabbix_get_problems", "zabbix_get_hosts", "zabbix_get_items", "zabbix_get_triggers", "radicale_list_calendars", "radicale_list_events", "radicale_get_event", "radicale_put_event", "radicale_delete_event", "radicale_move_event", "todoist_list_tasks", "todoist_list_projects", "todoist_create_task", "todoist_update_task", "todoist_complete_task", "todoist_capture_idea", "wiki_search", "wiki_read", "wiki_edit"],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -280,6 +379,15 @@
|
||||
"kimi-quota-footer": {
|
||||
enabled: true,
|
||||
},
|
||||
// Todoist idea capture (kb#170 component 1) — installed external
|
||||
// plugin, bind-mounted read-only from openai/todoist-capture-plugin
|
||||
// (see that project's docker-compose.yml adolf.volumes) onto
|
||||
// .openclaw/extensions/todoist-capture. Registers a `/idea` native
|
||||
// command; no hooks (no allowConversationAccess/allowPromptInjection
|
||||
// needed) — it POSTs straight to agap-mcp's /capture-idea route.
|
||||
"todoist-capture": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user