Skip to content
 
 

Latest commit

 

History

70 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kiyomizu - an AI companion, personified.

Special Thanks

  • LINUX.DO Indeed a very friendly community. Thank y'all at LINUX.DO for giving me inspiration and courage.

Precaution

  • This README.md is partially generated by Codex. I apologize for the potential inconveninece of comprehension due to the limitations of AI-generated text.
  • Kiyomizu is still a PoC. Treat it as a sharp tool, not a managed cloud product.
  • Localhost is still the cleanest deployment. VPS/LAN exposure is now guarded by default, but you should put TLS and ordinary network controls in front of it.
  • I'd be very grateful if you help make Kiyomizu more robust and more usable!

Kiyomizu is a local Kotlin/Ktor proxy for LLM APIs to turn a boring API call to a personified AI companion. It sits between your client and an upstream provider, rewrites requests when useful, preserves streaming, and exposes a small web UI for live configuration.

Kiyomizu manages stable prefix for a higher cache-hit rate, and help your AI companion recall memories that matters.

What It Does

  • Proxies OpenAI-style, Anthropic Messages, and Gemini generateContent requests.
  • Injects Anthropic prompt-cache breakpoints into stable conversation history.
  • Strips historical thinking and reasoning blocks to keep cacheable prefixes stable.
  • Preserves SSE and text streaming instead of buffering the full response.
  • Exposes a browser UI at / and a JSON config API at /api/config.
  • Protects /api/config with a config password. On public binds, first-run setup from the network is disabled unless explicitly allowed.
  • Requires proxy authentication on public binds, strips internal auth headers before forwarding, and rejects unsafe outbound URLs by default.
  • Includes an optional companion-memory layer backed by SQLite graph memory, local text search, and reflection summaries.

Architecture

The request flow is:

  1. Accept a request from your client.
  2. Detect the request envelope: Chat Completions, Responses, Anthropic Messages, or Gemini.
  3. Optionally rewrite the payload for cache stability and companion memory injection.
  4. Forward the request upstream with cleaned headers.
  5. Stream the upstream response back to the client.
  6. If companion memory is enabled, asynchronously extract memories from the latest exchange and store them in SQLite.

Main files:

Requirements

  • JDK 17 or newer
  • Network access to your upstream LLM provider
  • Gradle wrapper included in the repo

Run

./gradlew run

Default address:

http://127.0.0.1:8787

Useful endpoints:

  • /: configuration UI
  • /ui: same UI alias
  • /health: plain-text health summary
  • /api/config: config read/write API
  • /v1/models and related model-list paths: proxied upstream
  • /{...}: catch-all proxy route

Docker Compose

The Compose setup builds the fat jar in a container, stores the SQLite database in ./storage, and exposes Kiyomizu on ${KIYOMIZU_PORT:-8787}.

The Dockerfile expects gradle-9.5.1-bin.zip in the repository root. This avoids downloading the Gradle distribution during image build.

KIYOMIZU_CONFIG_PASSWORD='use-a-long-random-config-secret' \
KIYOMIZU_PROXY_PASSWORD='use-a-different-long-random-proxy-secret' \
UPSTREAM_URL='https://api.anthropic.com' \
docker compose up --build

For public or LAN binds, keep KIYOMIZU_ALLOW_UNAUTHENTICATED_PROXY=0 and put TLS in front of the service.

Reverse proxy + public domain (loopback bind)

A common VPS layout: Kiyomizu listens on 127.0.0.1:8787, Nginx terminates HTTPS for https://your.domain.example, and a local upstream (for example CPA at http://127.0.0.1:8317) sits on the same host.

In that setup:

  1. Set HOST=127.0.0.1 and KIYOMIZU_ALLOW_PRIVATE_UPSTREAMS=1 when the upstream is HTTP on loopback.
  2. Set KIYOMIZU_ALLOW_BROWSER_CORS=1 so the browser UI can call the API when opened via your public hostname (loopback bind disables CORS by default).
  3. Set KIYOMIZU_CORS_ALLOWED_HOSTS to the hostname(s) users type in the browser, comma-separated (host only or full https://… URLs are accepted). Example: KIYOMIZU_CORS_ALLOWED_HOSTS=your.domain.example. Without this, CORS only allows localhost / 127.0.0.1 and cross-origin requests from your public site will fail.

Example Compose file for Linux host networking: docker-compose.host-local.yml (network_mode: host, HOST=127.0.0.1, private upstream and CORS env vars wired).

Build And Test

./gradlew build
./gradlew test

Configuration

Kiyomizu reads startup configuration from environment variables and also supports live updates through /api/config.

By default, the companion SQLite database now lives in the OS data directory:

  • macOS: ~/Library/Application Support/Kiyomizu/kiyomizu_companion.db
  • Linux: ~/.local/share/Kiyomizu/kiyomizu_companion.db
  • Windows: %APPDATA%/Kiyomizu/kiyomizu_companion.db

You can override that path with KIYOMIZU_DB_FILE or the JVM property -Dkiyomizu.db.file=....

Core Settings

Variable Default Meaning
HOST 127.0.0.1 Bind address
PORT 8787 Server port
KIYOMIZU_CONFIG_PASSWORD empty Optional startup password override. Use at least 12 characters. /api/config requires this password via X-Kiyomizu-Config-Password or Basic auth
KIYOMIZU_PROXY_PASSWORD empty Optional dedicated proxy password. When HOST is not loopback, proxy routes require X-Kiyomizu-Proxy-Password or Proxy-Authorization; if unset, the config password is accepted instead
KIYOMIZU_ALLOW_UNAUTHENTICATED_PROXY 0 Dangerous escape hatch. Set 1 only behind a trusted reverse proxy or private firewall
KIYOMIZU_ALLOW_REMOTE_PASSWORD_SETUP 0 Allows first-run password setup on a public bind. Prefer KIYOMIZU_CONFIG_PASSWORD instead
KIYOMIZU_ALLOW_PRIVATE_UPSTREAMS 0 Allows http, localhost, private IPs, and metadata-like hosts for upstream/memory URLs. Keep this off on VPS unless you truly need a local model router
KIYOMIZU_ALLOW_BROWSER_CORS auto-local Enables browser CORS when public-bound. By default CORS is local-bind only
KIYOMIZU_CORS_ALLOWED_HOSTS empty Comma-separated hostnames (or https://host/… URLs) allowed in CORS when the UI is served via a reverse proxy on a public domain while Kiyomizu binds loopback. Always includes localhost and 127.0.0.1
KIYOMIZU_MAX_PROXY_REQUEST_BYTES 26214400 Maximum proxied request body size
KIYOMIZU_MAX_CONFIG_REQUEST_BYTES 131072 Maximum config/password request body size
KIYOMIZU_VERBOSE_HEALTH 0 Makes /health include config details. Default is only ok
PRESET custom custom or anthropic
UPSTREAM_URL empty Upstream base URL
OPENROUTER_BASE_URL empty Fallback upstream when UPSTREAM_URL is unset
CACHE_TTL 1h 5m, 1h, or none
CACHE_MODE explicit explicit or automatic
CACHE_STRATEGY stable-prefix stable-prefix or last
CACHE_BREAKPOINTS 4 Number of explicit cache markers, 0..4
SEND_TOP_LEVEL_CACHE_CONTROL 0 Add top-level cache_control even outside automatic mode
DYNAMIC_TAIL_MESSAGES 1 Tail messages excluded from the stable cacheable prefix
MEMORY_SUMMARY_WINDOW_ROUNDS 2 Prior user/assistant rounds fed to the summarization model alongside the current round, so it can resolve pronouns/deixis and catch facts surfacing across consecutive turns. 0 keeps the legacy single-round behaviour.
MEMORY_SUMMARY_WINDOW_MAX_CHARS 12000 Hard total-character cap on the assembled lookback history; when exceeded the oldest whole round is dropped (current round always kept intact).
STRIP_THINKING 1 Remove stored reasoning/thinking blocks from message history
MODEL_FILTER anthropic,claude Models eligible for Anthropic-specific patching
ANTHROPIC_BETA extended-cache-ttl-2025-04-11 Beta header sent on Anthropic Messages requests

Companion Memory Settings

Variable Default Meaning
MEMORY_ENABLED 0 Enables the memory system
MEMORY_SUMMARY_URL https://generativelanguage.googleapis.com Summary/state model base URL
MEMORY_SUMMARY_KEY empty API key for summary/state extraction
MEMORY_SUMMARY_MODEL gemini-2.5-flash Summary/state model
MEMORY_SUMMARY_PROMPT built-in prompt Extraction prompt for graph nodes, edges, and relationship deltas
MEMORY_DECAY_INTERVAL_HOURS 24 Decay/reflection job interval
MEMORY_DECAY_RATE 0.1 Strength decay applied to all memories
MEMORY_THRESHOLD 0.1 Memories below this strength are deleted
MEMORY_RECOVERY_AMOUNT 0.3 Strength restored when a memory is recalled
MEMORY_MAX_STRENGTH 1.0 Upper bound for memory strength
MEMORY_INITIAL_STRENGTH 0.8 Base strength multiplier for new memories
INTIMACY_DECAY_RATE 0.5 Daily relationship decay when interaction stops
MEMORY_DECAY_TAU_HOURS 360 Base Ebbinghaus-style decay constant
MEMORY_SALIENCE_K 1.0 Emotional salience multiplier
MEMORY_RECALL_MAX_NODES 6 Max normal recall clues injected per request
MEMORY_DEEP_RECALL_ENABLED 1 Enables explicit deep recall when the user asks to remember
MEMORY_DEEP_RECALL_MAX_CANDIDATES 40 Search budget for deep recall
MEMORY_DEEP_RECALL_MAX_CLUES 10 Max deep-recall clues injected into one response
MEMORY_PERSON_CONTEXT_MAX_CLUES 2 Max person-context clues injected per request

Presets

custom

custom is a dumb pipe by design. Kiyomizu forwards headers and payloads without Anthropic-specific surgery, aside from optional companion-memory injection when enabled.

Use it for OpenAI-compatible gateways, OpenRouter-style endpoints, local model routers, or anything else that should mostly be left alone.

anthropic

anthropic is opinionated. It:

  • rewrites Authorization: Bearer ... into x-api-key
  • ensures anthropic-version: 2023-06-01
  • injects explicit cache markers in explicit mode
  • can send top-level cache_control in automatic mode
  • strips reasoning blocks from prior history when configured

If you want Anthropic prompt caching to behave like a disciplined machine instead of a lottery ticket, use this preset.

Prompt Caching Strategy

stable-prefix is the default and the better choice. It treats the last DYNAMIC_TAIL_MESSAGES as volatile and spreads cache breakpoints across the older, stable region.

last is narrower. It places the effective cache boundary at the last stable message.

Thinking-strip behavior matters because reasoning blocks mutate constantly. Leaving them in history is a fine way to sabotage your own cache hits.

Companion Memory System

The memory layer is not a transcript archive. It is a graph-memory system with local search:

  1. After a response, Kiyomizu summarizes the latest user/assistant exchange with a separate model.
  2. That summary returns graph nodes, graph edges, and relationship deltas.
  3. Nodes are stored in SQLite as searchable text records, derived search terms, and lightweight graph links.
  4. Normal recall uses local search plus graph expansion. It does not call an embedding model.
  5. Deep recall runs only when the user explicitly asks Kiyomizu to remember or recall earlier interactions.
  6. A background maintenance job decays weak memories and can generate short reflection diary entries.

SQLite tables:

  • relationship_state
  • memory_nodes
  • memory_edges
  • memory_search_terms
  • reflections

The legacy memories table is still kept for database compatibility, but the active memory path no longer depends on embeddings or vector search.

Database file:

kiyomizu_companion.db

UI And Config API

Open the UI here:

http://127.0.0.1:8787/

Read config:

curl http://127.0.0.1:8787/api/config \
  -H 'X-Kiyomizu-Config-Password: your-password'

Update config:

curl -X POST http://127.0.0.1:8787/api/config \
  -H 'Content-Type: application/json' \
  -H 'X-Kiyomizu-Config-Password: your-password' \
  -d '{
    "preset": "anthropic",
    "upstream": "https://api.anthropic.com",
    "cache_mode": "explicit",
    "cache_strategy": "stable-prefix",
    "cache_breakpoints": 4
  }'

When password protection is enabled:

curl http://127.0.0.1:8787/api/config \
  -H 'X-Kiyomizu-Config-Password: your-password'

On first local run without KIYOMIZU_CONFIG_PASSWORD, open the Web UI and set the config password before reading or changing settings. On public binds, set KIYOMIZU_CONFIG_PASSWORD before startup; remote first-run setup is blocked by default.

Secrets are not exposed by the public config payload. The API reports whether memory keys are configured, but not their values.

VPS Safety

Run public deployments behind HTTPS. Kiyomizu does not terminate TLS.

When HOST is 0.0.0.0, ::, or any non-loopback address:

  • Proxy routes require X-Kiyomizu-Proxy-Password or Proxy-Authorization.
  • The first-run password setup endpoint is disabled unless KIYOMIZU_ALLOW_REMOTE_PASSWORD_SETUP=1.
  • Browser CORS is disabled unless KIYOMIZU_ALLOW_BROWSER_CORS=1.
  • Outbound URLs must be HTTPS public hosts with no credentials, query string, or fragment. Private networks and metadata services are blocked unless KIYOMIZU_ALLOW_PRIVATE_UPSTREAMS=1.
  • /health returns only ok unless KIYOMIZU_VERBOSE_HEALTH=1.

Example:

HOST=0.0.0.0 \
KIYOMIZU_CONFIG_PASSWORD='use-a-long-random-config-secret' \
KIYOMIZU_PROXY_PASSWORD='use-a-different-long-random-proxy-secret' \
UPSTREAM_URL='https://api.anthropic.com' \
./gradlew run

Client Setup Examples

Anthropic direct

  • Base URL: http://127.0.0.1:8787/v1
  • API key: your Anthropic key
  • Public bind: also send X-Kiyomizu-Proxy-Password or Proxy-Authorization if your client supports it. Otherwise put Kiyomizu behind a reverse proxy that performs authentication before traffic reaches it.

OpenAI-compatible client through a custom upstream

  • Base URL: http://127.0.0.1:8787/v1
  • API key: whatever your upstream expects
  • Preset: custom

Model list passthrough

Clients calling /v1/models can point at Kiyomizu directly; the request is proxied upstream.

Observability

Kiyomizu writes structured JSON request summaries to stdout. These logs include whether a request was patched, how many thinking blocks were removed, and where explicit cache breakpoints were inserted.

That is enough to answer the only question that matters: did the proxy help, or did it merely make noise.

Notes

  • The server allows local/private-network CORS only for local binds by default.
  • Streaming text and SSE responses are passed through without full buffering.
  • The memory database is created on startup when needed and tightened to owner-only permissions on POSIX filesystems.
  • The project uses Kotlin 2.0.21, Ktor 2.3.12, and SQLite via org.xerial:sqlite-jdbc.

About

Kiyomizu - AI companion, personified.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages