Files
ai-memory/docker/docker-compose.yml
Lucas Oliveira 53180c8d51 feat(llm): send operator headers and identify ai-memory on every request
OpenCode Zen/Go notified operators that requests missing an
`x-opencode-session` header may start erroring, and reported ai-memory's
traffic as "Unknown client — your requests carry no user agent, so we
can't tell what sends them". Both halves are real: `reqwest` sends no
`User-Agent` unless one is configured, and nothing in the crate could
attach a caller-supplied header.

Add `AI_MEMORY_LLM_HEADERS` (`llm_headers` in config.toml): comma-separated
`Name=Value` / `Name: Value` entries, parsed and validated once at the
configuration boundary into a typed `ExtraHeaders`, then sent on every chat
request. This follows the rule provider auth already obeys — providers
consume typed material and never re-parse operator strings — and means a
malformed entry fails at startup rather than on the first consolidation
pass. Headers ai-memory sets itself are refused rather than duplicated:
`RequestBuilder::header` appends, so a second `authorization` would break
the request instead of overriding it. Values are marked sensitive and never
logged; `Debug` prints names only.

Two defaults ride on the same mechanism, layered *under* the operator's so
an explicit entry always wins:

- `User-Agent: ai-memory/<version>` on every provider. Not opencode-specific:
  an unattributable request is the one a rate limiter throttles first, and
  any gateway benefits from knowing what called it. Copilot is excluded — it
  keeps `GitHubCopilotChat/<version>`, the agent GitHub's API expects.
- `x-opencode-session` on the `opencode` provider, one id per process, since
  that header is Zen/Go's own request-correlation field.

Zen/Go permits this. Its documentation ("Where can I use it?", docs/go.mdx)
says Go "is designed to be used with OpenCode and other popular coding
agents that produce a similar types of requests", documents the
`https://opencode.ai/zen/go/v1/...` endpoints for direct use, and asks that
the calling tool "does not generate abusive traffic" and "properly
identifies itself (no broad user agents)". Hence naming ai-memory in the
agent string rather than copying OpenCode's own — identifying the caller is
the requirement, and impersonation would defeat it.

Integration tests drive `build_provider` against wiremock: asserting the
header is on the struct is not the same claim as asserting the right single
value is on the wire.
2026-09-03 12:02:07 +00:00

50 lines
1.7 KiB
YAML

# docker-compose.yml for ai-memory.
#
# Run from this directory:
# docker compose up -d
# Attach Claude Code (or any MCP HTTP client) at
# http://localhost:49374/mcp
# State persists in the `ai-memory-data` volume; move it between hosts
# by stopping the container, `docker volume export` (or rsync the
# mountpoint), and importing on the target.
services:
ai-memory:
image: ai-memory:local
build:
context: ..
dockerfile: docker/Dockerfile
container_name: ai-memory
restart: unless-stopped
user: "1000:1000"
volumes:
- ai-memory-data:/data
ports:
# Bind to loopback by default; flip to 0.0.0.0:49374 when serving
# the homelab LAN. Keep behind a reverse proxy if you do.
- "127.0.0.1:49374:49374"
env_file:
- path: .env
required: false
environment:
- RUST_LOG=ai_memory=info,ai_memory_store=info,ai_memory_wiki=info,ai_memory_mcp=info,tracing_appender=warn
# LLM provider configured via docker/.env (gitignored). Options:
# anthropic | openai | openai-oauth | anthropic-oauth | copilot |
# gemini | openai-compat | opencode
# See .env.production.example for the full reference.
#
# Extra headers on every LLM chat request, for a gateway that requires
# a caller-identifying one. Comma-separated `Name=Value` entries; the
# default `User-Agent: ai-memory/<version>` needs no configuration.
# - AI_MEMORY_LLM_HEADERS=x-opencode-session=homelab-01
healthcheck:
test: ["CMD", "/usr/local/bin/ai-memory", "status"]
interval: 30s
timeout: 5s
retries: 3
start_period: 5s
volumes:
ai-memory-data:
name: ai-memory-data