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>
Listener auth (kb#180, DESIGN-a2a-agents.md §4)
-----------------------------------------------
agap-mcp binds :3100 on every interface (network_mode: host) and the LAN
carries VPN-terminated peers, so an unauthenticated JSON-RPC listener handed
ha_call_service / gitea_wiki_write / wiki_edit / radicale+todoist writes and
POST /capture-idea to any LAN peer. Only vw_* was gated before (kb#147), and
only at ENFORCE=1.
src/listener-auth.js now requires `Authorization: Bearer <token>` resolving to
a known agent id on every route except /health, which stays open so a
misconfigured token map is still diagnosable. Two gates stay deliberately
layered and independently switchable: "are you an agent at all?" (this file)
vs "are you trusted enough for the vault?" (trust-gate.js), both reading the
same token map.
Also closes an SSE session-hijack hole: /messages previously trusted any
sessionId with no credential, so a guessed or leaked id was full tool access.
Sessions are now pinned to the caller identity captured at the /sse handshake,
comparing agent id *and* token.
Auth defaults ON, and boot fails loudly if the token map is empty rather than
serving 401 to everyone while /health reports ok. Rollback is
AGAP_MCP_REQUIRE_AUTH=0.
Verified live: unauthenticated and bad-token /mcp -> 401, unauthenticated
/capture-idea -> 401, /health -> 200, both real agent tokens -> 200 with 36
tools, including from inside the adolf container.
Pin the bw CLI
--------------
The Dockerfile installed @bitwarden/cli unpinned. Rebuilding jumped
2026.2.0 -> 2026.7.0, whose WASM cipher deserializer rejects any stored login
carrying `"uri": null` ("invalid type: JsValue(Object({...})), expected a
string") -- 33 of 49 items in this vault have that shape. `bw list` then exits
1, server init fails, and the container crash-loops. Pinned to 2026.2.0.
Do not unpin: 2026.7.0 cannot authenticate against this Vaultwarden
(2025.12.0) at all -- it refuses plain HTTP outright and 404s on the identity
endpoint over HTTPS. Updating the CLI requires upgrading Vaultwarden first.
capture / classifier
--------------------
Adds the POST /capture-idea REST endpoint and the idea classifier behind it
(consumed by the todoist-capture plugin), with tests. Carried in the same
commit because server.js wires both this and the auth boot path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Logs, __pycache__/*.pyc and timestamped *.bak-* snapshots had accumulated
untracked for weeks. They churn on every run, so tracking them would only
produce noisy diffs; ignore them instead.
Also adds agap-mcp/.env (holds the kb#180 AGAP_MCP_AGENT_TOKENS map and
BW_PASSWORD) and a bare .env alongside the existing per-service entries.
Untracks family/__pycache__/migrate.cpython-312.pyc, the one already-tracked
file matching the new rules, so ignored and tracked state don't disagree.
Left on disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Retain/extraction moved off the Kimi wrapper (hindsight-llm:8012) onto
ollama/gemma3:4b via LiteLLM, joining consolidation + reflect which were
already local (kb#88). Kimi is no longer in the Hindsight path at all, so
the memory backend consumes zero quota and Adolf's 5h window is left
entirely for conversation.
Accepted tradeoff: kb#84/kb#88 kept retain on Kimi for fact quality, since
gemma3:4b's Russian fluency was never verified and this bank is largely
Russian. A dry-run extraction on Russian input after the change returned
clean, parseable output but captured only one of two facts in the sample,
so extraction is coherent but lossier. Revert this first if facts degrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
vaultwarden.js run() passed BW_PASSWORD as argv to `bw login`/`bw unlock`.
On any non-zero exit, execFileSync throws an Error whose message is
"Command failed: bw unlock <BW_PASSWORD> --raw", which propagated to
server.js's `console.error('Init failed:', e.message)` -> the master password
landed in container stdout / `docker logs` on every Vaultwarden init failure
(wrong password, server down, TLS reset). Empirically confirmed by a security
audit 2026-07-24.
run() now catches the exec error and re-throws with the argv stripped: only the
subcommand, exit code, and stderr survive, and BW_PASSWORD is scrubbed from
stderr defensively. Verified: a forced failure yields "bw unlock failed (exit 1)"
with no password substring.
Not yet active: the running agap-mcp container predates this file; a rebuild
(`docker compose build agap-mcp && docker compose up -d agap-mcp`) is needed to
deploy it. The live container is still vulnerable until then.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commits a cluster of entangled agap-mcp / Adolf-tooling WIP that had accumulated
uncommitted in shared files (server.js, the three MCP-config layers). Bundled as
one commit because server.js interleaves all of it and cannot be cleanly split;
each stream is named here for the record. Authorized by alvis 2026-07-23.
- **kb#95 — family MediaWiki tools:** new src/mediawiki.js (wiki_search / wiki_read
/ wiki_edit, MediaWiki login->CSRF->edit flow, no new deps), registered in
server.js and fetched from the family.alogins.net Vaultwarden login item.
Proven standalone against family.alogins.net (search/read/edit, revid 1520 on a
bot-userspace page). Wired into all three layers: openai/shared-mcp.json,
adolf/openclaw.json, openai/agent-registry.yaml.
- **kb#147 — vault trust-gate (A2A-15), DORMANT:** new src/trust-gate.js (+ two
test files), requireVaultAccess() around the vw_* tools, gated by
AGAP_MCP_ENFORCE_VAULT_TRUST (docker-compose.yml, default 0). OFF by default —
vw_* behaviour is byte-for-byte unchanged until an operator sets ENFORCE=1 and
populates AGAP_MCP_AGENT_TOKENS from Vaultwarden. That activation is a separate
human step; kb#147 remains escalated for human verification and is NOT verified
by this commit. js-yaml added to read the registry. agent-registry.yaml mounted
read-only as the trust-class source of truth.
- **Todoist tools:** new src/todoist.js (initTodoist + 6 todoist_* tools),
registered in server.js, sourced from the TODOIST_TOKEN Vaultwarden item.
- **kanboard cutover cleanup:** removes src/kanboard.js and its imports — the
kanboard_* slice moved to the standalone kanboard-mcp on 2026-07-06.
- **openai/validate_capability_grants.py:** cross-checks the registry against the
live openclaw.json + shared-mcp.json layers; passes (exit 0).
No secrets committed: all tokens come from Vaultwarden via env/.env; the trust
gate's AGAP_MCP_AGENT_TOKENS defaults to `{}` (fail-closed). node_modules/ now
gitignored, package-lock.json tracked.
NOT YET ACTIVATED: agap-mcp has not been rebuilt and adolf-llm/adolf not
restarted, so the wiki/todoist tools are wired but not live. That restart is the
outstanding step on kb#95 (and stays a human/orchestrator action).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two related changes to the /usage quota route, committed together because they
are entangled in the same code path.
1. Stop refreshing the Kimi OAuth token from this route (kb#87). Was already
present as uncommitted working-tree WIP, not authored in this commit's
session. Kimi rotates the refresh_token on every refresh (single-use), so an
independent refresh here invalidated the copy the CLI's creds file holds ->
the CLI's next refresh failed invalid_grant and wiped the whole login (the
recurring Adolf logout, incl. the 2026-07-17 06:15 wipe / task #86). Removes
KIMI_OAUTH_HOST, KIMI_CLIENT_ID, refreshKimiToken() and the kimiMemToken
cache; the CLI is now the sole refresher and this route only ever READS.
2. Serve the last good reading when the token is stale, instead of erroring.
Measured 2026-07-22: the access token's expires_in is 900s, so it is only
valid for 15 minutes after the CLI last refreshed it -- i.e. only within 15
minutes of an actual Adolf turn. Adolf is idle most of the day, so bare
reads failed far more often than they succeeded and quota gating was
effectively blind. /usage now caches every success and, on a stale token,
returns that payload with stale/as_of/age_s/stale_reason so callers can
judge whether it is fresh enough. Cache is mirrored to the workspace volume
so it survives restarts, and writes are best-effort so an unwritable volume
cannot break the route. Auth behaviour is unchanged by this half.
Payload shape is additive only -- existing kimi-usage -q filters keep working.
Verified live after rebuild: fresh read returns weekly 16% / 5h 5% with
stale:false; cache file written to /workspace/.adolf-llm/usage-cache.json;
kimi-usage -q '.window_5h.pct' returns 5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per DESIGN-a2a-agents.md v2.1 §2, §5, §5b: an agent is a persona, not a
queue. Sibling of kb#133's model-registry.yaml, same load/get/CLI shape.
- agent-registry.yaml: populates adolf (trusted, scoped core tools),
claude-coder (trusted, implementer), torgash (sandboxed, marketplace-
only, target state - not yet built), researcher (sandboxed/untrusted-
input, target state), kimi-endpoint/local-small-endpoint (trivial
model-agents from #133), and alvis/elizaveta as first-class human
agents with trust=human, own inboxes, own private memory bank targets
(adolf-alvis/adolf-elizaveta/adolf-shared - split itself is kb#153).
Also carries trust_classes with numeric rank, a runtimes: section for
Claude Code (kb#133 explicitly excluded it from model-registry.yaml),
and memory_bank_policy documenting §5b's hard rules.
- agent_registry.py: effective_card() is what makes "backbone swap is
one field" true rather than aspirational - tier/cost_class/lifecycle
are never stored statically on an agent, they're resolved at read
time from `backbone` via model-registry.yaml or runtimes:. Also
trust_rank() and can_reach_vault() (kb#147's vault-access DECIDED
rule as a checkable function, not just a comment).
Several Cards (torgash, researcher, elizaveta's KB identity, the split
memory banks) describe target state for things not yet built/migrated -
flagged with status/note fields rather than implied live, matching the
task's scope (registry only, no runtime migration).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
Per DESIGN-a2a-agents.md v2.1 §2-3b: models are the scarce queued
resource, version-controlled here rather than hardcoded in callers.
- model-registry.yaml: kimi (main reasoning, quota-gated), local-small
(ollama/gemma3:4b, always-on cheap tier), bge-m3 (embedder + routing
classifier, never-evict), tei-reranker (never-evict, interactive-
critical), paid-fallback (metered, opt-in only, unreachable by
default via empty routing.metered_opt_in). GPU residency policy
carries the never-evict set, co-residency groups, and measured
baseline (bge-m3+gemma3:4b+tei-reranker ~6.2/8GB on the GTX 1070).
- model_registry.py: resolve(tier) picks an available model without
the caller naming one, gated so a metered model is only reachable
with both allow_metered=True and an opted-in virtual key;
to_probe_config() bridges registry quota data into kb_worker.py's
existing Probe classes (no duplicated probe logic); preload_check()
expresses the §3b pre-load VRAM check purely from registry data.
Gap noted for follow-up: bge-m3 has no litellm-config.yaml model_list
entry yet (embedder there still points at ollama/nomic-embed-text on
a different port) — out of scope here, registry documents it as-is.
Per-stage LLM routing so Hindsight's high-volume background jobs stop burning
the rate-limited Kimi subscription:
- CONSOLIDATION + REFLECT -> ollama/gemma3:4b via LiteLLM :4000 (local GPU, free)
- RETAIN (fact extraction) stays on Kimi (hindsight-llm) for quality
Fixes the Kimi 5h window maxing at 100% from ~1100 background calls/3h (dropped
to ~38%). Documents the reasoning in HINDSIGHT-MIGRATION.md section 10:
frequent/mechanical/background stages -> cheap local model; user-facing,
quality-critical, low-volume -> Kimi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
The bridge advertised non-existent gateway actions ("click"/"type"), so Kimi
sent action="type" -> "Unknown action". Corrected to the real vocabulary:
interactions go through action="act" with args={kind, ref, text} (e.g.
{kind:"type", ref:"e59", text:"..."}, {kind:"click", ref}), plus the real
top-level actions (open/navigate/snapshot/screenshot/act/...). Verified Adolf
can now type/click in the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
- openclaw.json: browser.enabled + noSandbox (containers can't sandbox) +
ssrfPolicy.dangerouslyAllowPrivateNetwork (local *.alogins.net resolve to
the private host gateway); "browser" added to gateway.tools.allow.
- docker-compose.yml: extra_hosts family.alogins.net / wiki.alogins.net ->
host-gateway on both adolf and adolf-llm, so the gateway browser AND the
Kimi CLI's fetch reach the local wikis (hairpin-NAT dodge, like matrix).
Enables Adolf to open + log into the family wiki (kb#64). Root 401 on the
openclaw-tools bridge was a separate fix (empty ADOLF_GATEWAY_TOKEN -> just
recreate the bridge container; no file change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
BW_PASSWORD referenced via ${BW_PASSWORD} from a git-ignored .env instead
of plaintext in the tracked compose. Also records the account repoint to
adolf46@proton.me (the shared bw dir's actually-working account; the old
allogn creds were stale). Restores Claude's vault MCP and backs Adolf's
vault access (kb#64).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
H4 (kb#76) - Cognee -> Hindsight migration finished:
- New openai/hindsight-llm/ (clone of cognee-llm, :8012, own volume) so
Hindsight's Kimi LLM no longer depends on the cognee stack
- Repointed hindsight service LLM at hindsight-llm:8012 (+ depends_on)
- Removed cognee, cognee-mcp, cognee-llm services + cognee-llm-home volume
from openai/docker-compose.yml
- Removed the disabled cognee-memory plugin entry from openclaw.json
Vault access (kb#64): wired the shared agap-mcp (:3100, same MCP Claude uses)
into Adolf's registry - "agap" server in shared-mcp.json + openclaw.json
mcp.servers. Adolf can now fetch credentials from Vaultwarden (verified).
Note: agap-mcp/docker-compose.yml (repointed to the adolf46 account) is
deliberately NOT in this commit - it holds the master password in plaintext.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
Relocate the OpenClaw gateway config from openai/adolf/ to adolf/ at
the repo root, since it's shared config rather than part of the
openai/ compose project's own tree. Update the docker-compose.yml
bind-mount path (./adolf/openclaw.json -> ../adolf/openclaw.json) and
comments, plus README.md references, to match. Verified: adolf
container recreated healthy with the new bind-mount source resolving
to /home/alvis/agap_git/adolf/openclaw.json, and a fresh
openclaw.json.last-good snapshot confirms the config was accepted.
Replace nomic-embed-text (768-d) with bge-m3 (1024-d, GPU-served via the
same :11436 ollama) as cognee's embedding model, for better multilingual
recall. cognee's Qdrant collections held only P4 smoke-test fixtures (no
real conversation data — adolf-llm's cogneeSearch/cogneeAdd are still
stubs), so the stale 768-d collections were dropped and left for cognee
to recreate at 1024-d on next write, rather than migrated.
Verified: bge-m3 returns 1024-dim vectors via ollama /api/embed; after
recreating the cognee container, a remember/recall round trip (including
Russian text) produced correctly dimensioned (1024-d) Qdrant collections
and recalled the exact fact stored, then the test dataset was deleted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Build adolf:local with the matrix extension bundled (OPENCLAW_EXTENSIONS),
route mtx.alogins.net to host-gateway to dodge hairpin NAT, and wire
ADOLF_KEY/Matrix creds through openai/.env (now gitignored + untracked;
it previously held Langfuse keys in cleartext git history). Runtime
openclaw.json + SOUL.md live in the adolf-state volume, not this repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
- adolf-llm/server.js now loads SHARED_MCP_SERVERS from the mounted
/shared-mcp.json instead of a hardcoded stub, so adding a shared MCP
server is a one-file change. Verified end-to-end: a real chat-completions
turn writes a session .mcp.json containing both cognee and openclaw-tools
entries (kimi itself still needs `kimi login` in adolf-llm-home, unrelated
to this change).
- Documented the Gate-1 transport reconciliation: decompiled the installed
@moonshot-ai/kimi-code package to confirm its .mcp.json schema keys remote
servers on `transport` ("stdio"/"http"/"sse", inferred as "http" from a
bare `url`, never "sse"), while OpenClaw's own canonical mcp.servers schema
uses different literals ("streamable-http"/"sse") for the same field name
and treats `type` as a CLI-native alias it normalizes itself. `type: "http"`
is the one shape both consumers tolerate, so shared-mcp.json keeps it.
- New openai/openclaw-tools/ service: a stateless MCP-over-Streamable-HTTP
bridge (Node, @modelcontextprotocol/sdk) exposing message_send, cron_create,
cron_list, nodes_invoke, and browser_invoke, each proxying to the OpenClaw
gateway's POST /tools/invoke. Verified initialize + tools/list handshake and
a tools/call against the not-yet-running `adolf` gateway returns a clean
isError content instead of breaking the MCP connection. Documented that
cron/nodes are hard-denied on that HTTP surface by default until P6 adds
them to gateway.tools.allow; message/browser are not similarly restricted.
- Wired openclaw-tools into docker-compose.yml (openai network, :8020) and
added its shared-mcp.json entry alongside cognee.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Resolves the 4 P4 blockers and wires cognee/cognee-mcp into the openai
compose stack:
- qdrant: container was gone (data intact under /mnt/ssd/dbs/qdrant);
brought back up, confirmed healthy on :6333.
- Embeddings: switched from a dead LiteLLM route to ollama directly
(host.docker.internal:11436, nomic-embed-text, 768-dim), using cognee's
dedicated OllamaEmbeddingEngine and its native /api/embed endpoint.
Requires extra_hosts: host.docker.internal:host-gateway since ollama
lives in a separate compose project.
- cognee-llm kimi auth: root cause was that cognee-llm had never been
started, so its kimi-agent-home-equivalent volume didn't exist yet.
Seeded cognee-llm-home from the already-authed kimi-agent-home volume
(read-only copy of config/credentials/oauth/device_id); cognee-llm now
serves real completions.
- mkdir'd cognee data/system dirs: confirmed present (done by user).
Also fixed three issues found only during a live end-to-end smoke test:
- VECTOR_DB_PROVIDER must be a real container env var, not just present in
the mounted cognee.env — the qdrant adapter's sitecustomize.py
registration hook reads os.environ directly, which pydantic-settings'
env_file parsing never populates.
- Baked the Kuzu/Ladybug JSON extension into the cognee image. This
deployment's egress to extension.ladybugdb.com is bandwidth-throttled to
~1.2 KB/s, so cognee's own runtime auto-download reliably timed out,
leaving /health permanently unhealthy and graph queries failing. Fetched
the ~827KB extension out-of-band (16-way parallel ranged GETs) and added
it to the image via COPY.
- LLM_ENDPOINT needed an explicit /v1 suffix (litellm appends
"/chat/completions" verbatim) and LLM_INSTRUCTOR_MODE=json_mode is
required since cognee-llm's Kimi wrapper is a text-only pass-through with
no real tool-calling support.
Verified with a full remember -> recall round trip through cognee-mcp's
MCP tool surface: stored a fact containing a codeword, recalled it via
GRAPH_COMPLETION search, got the exact codeword back. Exercises cognee-llm,
ollama embeddings, Qdrant, and Kuzu together.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Reverse the earlier 'default to LiteLLM' recommendation: per user intent,
cognee runs its LLM on the Kimi subscription via cognee-llm (the reason the
wrapper exists). Gate-5 latency is an accepted tradeoff; LiteLLM stays a
documented fallback. Embeddings remain on LiteLLM nomic-embed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Model backend for the Adolf gateway. OpenAI-compatible (model 'adolf'), real
SSE streaming, chat_id session-keying (parsed from OpenClaw's untrusted-metadata
block per SPIKE gate 2) -> 1:1 kimi -r resume, media persistence for the CLI's
ReadMediaFile, per-session project-root .mcp.json (gate 1; no --mcp-config-file).
Cognee auto-memory hooks and shared-MCP server list are non-blocking stubs with
TODO(P4/P5) markers. New service + workspace/home volumes in compose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Stateless one-shot wrapper for Cognee batch cognify: fresh temp dir per
request, no resume, non-streaming, text-only, bounded concurrency (3).
Per SPIKE-FINDINGS gate 5, Cognee should default its LLM to LiteLLM; this is
the optional low-volume path. New service + cognee-llm-home volume in compose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
New 'adolf' service builds ../../adolf (the OpenClaw fork), runs the gateway
(node dist/index.js gateway --bind lan --port 18789) with persistent
adolf-state volume. Skeleton only; Matrix/SOUL.md/model-provider config wired
in P6. Existing services untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Kanboard service docs, orchestration rules, quota script, and a reference
copy of the MCP tool implementation have been consolidated into a new
dedicated repo (alvis/kanboard, cloned to /home/alvis/kanboard). This
directory now documents only the live docker-compose config that stays
here.
Wrapper previously passed only the last user message to a fresh 'kimi -p'
per request, so both the OpenWebUI thread history and kimi's own session
state were dropped every turn.
Now each conversation is keyed by a content-hash chain over the messages
array (survives LiteLLM in between) and mapped to a persistent kimi session
resumed via 'kimi -r <id>', with the session_id captured from stream-json
meta. Each conversation also gets its own working dir under
/workspace/conversations/<id> so file state is isolated and persists across
turns. Map persisted to /workspace/.kimi-agent/sessions.json (LRU-capped);
falls back to full-transcript reseed if a mapping is missing.
Open WebUI's OpenAI connection to LiteLLM was silently 401ing on every
request (OPENAI_API_KEY=dummy vs LiteLLM's real master key), so none of
the litellm-routed models (judge, tip-generator, kimi-agent, OpenRouter
free tier) ever appeared in the model picker -- only the direct Ollama
connection's models did. Key is now sourced from openwebui/.env
(gitignored), matching the langfuse key pattern already used elsewhere.
Runs the kimi-code coding agent in its own container, exposed as an
OpenAI-compatible model ("kimi-agent") that LiteLLM/Open WebUI can call
directly. Backed by the user's own Kimi/Moonshot subscription via
`kimi login`, not the pay-per-token API. Mount is scoped to a dedicated
~/kimi-workspace directory rather than the full home dir.
Accumulated uncommitted infra changes:
- Caddyfile: repoint HA/Zabbix to 192.168.1.4/.3, add ~20 new site routes
- Immich: move media to /mnt/smsg, enable CUDA ML, mem limits, rewrite backup.sh
- Add service stacks: agap-mcp, anki, family, freshrss, iperf3, kanboard,
linkwarden, qbittorrent, radicale, syncthing, vikunja, windows
- openwebui: enable API keys; ollama: drop CPU fallback
- seafile/zabbix: extra_hosts entries; matrix: add user juris
- Remove pihole stack and stale wiki/migrate.py
- Ignore marketplace-mcp (standalone repo) and linkwarden runtime data
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Ollama runs on port 11436, not the default 11434. Add explicit
OLLAMA_BASE_URL=http://host.docker.internal:11436 so open-webui
finds the models. Also gitignore .env (holds ANTHROPIC_API_KEY).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move services out of the monolithic openai/docker-compose.yml:
- ollama/ — ollama GPU + CPU inference servers
- openwebui/ — open-webui chat UI (uses env var for ANTHROPIC_API_KEY)
- searxng/ — SearXNG container + searxng-mcp MCP server (port 3102)
openai/ now contains only: litellm, langfuse, qdrant, faster-whisper,
silero-tts, pipecat.
searxng-mcp exposes a single searxng_search tool via MCP HTTP on :3102.
Registered in ~/.claude.json as the "searxng" MCP server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LiteLLM proxy with langfuse callbacks, postgres backends, and OpenRouter fallbacks.
- Langfuse observability UI.
- Pipecat voice pipeline (LiveKit + STT + TTS + LLM) and Silero TTS build contexts.
- Ollama tuned for GPU (OLLAMA_NUM_GPU=999, mem_limit=4g, max 2 loaded models).
- open-webui wired to litellm + faster-whisper + silero for voice.
- litellm-config.yaml publishes oO's model aliases (tip-generator, embedder, judge)
pointing at the host ollama on :11434 so ml/serving can call them via LiteLLM.
.env skipped (secrets).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Docker deployment of oh-my-opencode connected to Bifrost LLM gateway
via the adolf_default network. Configured with local Ollama models
(qwen3:8b default) — no cloud provider dependencies.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backup.sh: mysqldump all 3 DBs + rsync seafile-data, runs every 3 days
via root crontab, keeps last 5 backups in /mnt/backups/seafile
- Notifies Zabbix trapper item seafile.backup.ts (id 70369) on AgapHost
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add extra_hosts: docs.alogins.net:host-gateway so OnlyOffice container
can reach Seafile's callback URL without going through the public IP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backup.sh: runs every 3 days via root crontab, uses built-in container
backup command, copies db/config/rsa_key to /mnt/backups/vaultwarden,
keeps last 5 backups, notifies Zabbix item vaultwarden.backup.ts (id 70368)
- Zabbix trigger fires if no backup received in 4 days
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add vaultwarden/docker-compose.yml (port 8041, data on /mnt/ssd/dbs/vw-data)
- Update Caddyfile with all current services including vw.alogins.net
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Daily backup at 02:30 via root cron: DB dump + rsync of library/upload/profile
to /mnt/backups/media/. Retains 14 days of DB dumps. Monitored via Zabbix
immich.backup.age item with High trigger if stale >25h.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>