Local, private AI knowledge base — runs completely offline on your machine by default. Documents and web pages are converted into vectors and can be queried via chat with a local LLM or optionally OpenAI / Gemini / Claude.
KnowSora is a self-hosted AI assistant with RAG knowledge bases, nine interchangeable LLM providers (including xAI Grok and OpenRouter), a skill system with built-in tools (AI image and video generation, project file access for any AI, shell execution), project directories with universal file preview, and a web terminal for SSH access.
Documentation as of July 2026 — if something looks different in the app, the app takes precedence. Current version includes:
manage.sh (UGREEN-compatible).env.example---
# 1. Get the code or extract the ZIP
cd /volume3/docker/knowsora
unzip -o knowsora_release.zip # if delivered as a ZIP
# 2. Start — manage.sh handles .env + SECRET_KEY + Docker
./manage.sh start
# 3. In the browser
http://<NAS-IP>:47822
On first start, admin / admin is created — go straight into the admin area after login and set your own password.
What ./manage.sh start does automatically:
.env from .env.example (if not already present).env.example are added to yourexisting .env (old values are preserved)
SECRET_KEY if empty (via openssl / python3 / /dev/urandom)and stores it persistently in /data/.secret_key
./manage.sh logs---
without local LLM, 4 GB is sufficient)
/dev/dri for VAAPI acceleration on thelocal LLM (Intel N100, Pentium Gold 8505, N-Series)
Received a new release ZIP → simply place it in the project directory and update:
cd /volume3/docker/knowsora
# place knowsora_release.zip there (e.g. via scp/SFTP/web upload)
./manage.sh update
manage.sh update automatically does:
manage.sh itself(tries unzip → python3 → python → docker run alpine, depending on what's available on the host — also works on UGREEN/Synology without the unzip package)
.env.example into .env(existing values are NEVER overwritten, only new keys are added)
unprivileged_userns (typical for NAS)
For build cache issues or broken images:
docker compose down
docker rmi knowsora-backend:latest knowsora-frontend:latest
./manage.sh update
Build logs are output with --progress plain — no more animated spinners, each step appears once as a plain text line (important for SSH sessions without a fully interactive TTY).
---
./manage.sh start automatically creates .env from .env.example and generates the SECRET_KEY. Only edit manually if you want to change defaults (ports, data path, timeouts, provider keys in advance).
Default fields you may want to adjust:
| Variable | Meaning | Example / Default | |---|---|---| | SECRET_KEY | JWT signature + derivation of the encryption key | Auto-generated if empty | | FRONTEND_PORT | Port of the web UI | 47822 | | BACKEND_PORT | Port of the API | 48823 | | LLAMA_CHAT_PORT | Port of the local LLM | 48824 | | ADMIN_PASSWORD | Initial password for the admin user | admin (change immediately!) |
Optional but recommended:
| Variable | Default | Purpose | |---|---|---| | HOST_UID / HOST_GID | 1000:1000 | UID/GID under which the containers run — adjusts file permissions on your NAS volume | | DATA_DIR | /data | In-container path for persistent data | | MEDIA_ROOT | /data/media | AI-generated images/videos | | UPLOAD_DIR | /data/uploads | Chat attachments + provider output files | | LLM_HTTP_TIMEOUT | 3600 | Seconds — for long AI responses | | CLAUDE_CLI_TIMEOUT | 3600 | Claude Code CLI hard cap per call | | CODEX_CLI_TIMEOUT | 3600 | Codex CLI hard cap per call | | GEMINI_CLI_TIMEOUT | 3600 | Gemini CLI hard cap per call | | VEO_MODEL | veo-3.0-generate-001 | Video generation model — veo-2.0-generate-001 if Veo 3 is not enabled | | VEO_MAX_POLL_S | 600 | Maximum wait time for video rendering | | PROJECT_SHELL_TIMEOUT | 90 | Seconds — hard cap for the project_run_shell tool | | CODEX_SANDBOX_MODE | _(empty)_ | NAS workaround: danger-full-access if kernel.unprivileged_userns_clone=0 (UGREEN/Synology). Values: read-only, workspace-write, danger-full-access |
Important regarding encryption: Never changeSECRET_KEYif there's already data in the DB. Otherwise all encrypted API keys become unusable and all sessions have to be logged in again. On the first start, the key is automatically persisted to/data/.secret_key— both paths (.envand.secret_key) should be part of your backup. The Fernet encryption key for API keys in the DB is derived fromSECRET_KEYvia PBKDF2 — there is no separateFERNET_KEYvariable.
---
manage.sh is more than just a docker compose wrapper. On every start or update, the following run automatically:
If a knowsora_release.zip is placed in the project root that's newer than the last extracted version, manage.sh extracts it itself. Fallback chain for hosts without the unzip package:
1. unzip Standard on Linux with zip tools
2. python3 -m zipfile wherever Python 3 is available (e.g. UGREEN)
3. python -m zipfile Python 2 fallback
4. docker run alpine last resort — Docker is there anyway
After successful extraction, the ZIP's mtime is set 24 hours in the past so the auto-trigger doesn't fire endlessly. On the next re-upload of a ZIP (newer mtime), it triggers again.
Log output:
[KnowSora] New knowsora_release.zip detected — extracting automatically...
[KnowSora] → 'unzip' missing, using python3 zipfile
[OK] knowsora_release.zip extracted
.env.exampleAdds missing ENV variables from .env.example into your .env without overwriting existing values. Only keys that are completely missing in .env are appended — commented-out values (# FOO=...) are considered "deliberately disabled" and are not activated.
Added keys are placed at the end of .env under:
# ─── Automatically added from .env.example ───
This way you automatically get all new config options on updates without losing your own values.
Automatically detects when the host kernel has unprivileged_userns_clone=0 (typical for UGREEN, Synology, some Docker setups) and sets CODEX_SANDBOX_MODE=danger-full-access in .env.
Background: Codex CLI uses bubblewrap as a sandbox. On kernels without unprivileged user namespaces, bwrap fails with "No permissions to create a new namespace" and Codex aborts. The container is isolated by Docker anyway, so there's no real security loss.
Logic: only if CODEX_SANDBOX_MODE= is empty in .env AND sysctl kernel.unprivileged_userns_clone == 0. If you've set the value manually (whether danger-full-access, read-only, or something else), it won't be touched.
---
KnowSora supports nine providers — all usable in parallel. Selection per chat via the provider pill in the top left.
Runs in the knowsora-llama-chat container. Model selection: place a GGUF file in /data/models/, set in .env:
CHAT_MODEL_PATH=/models/qwen3-4b-q4_k_m.gguf
Restart the container. Recommendation for 4 GB mode: Qwen3-4B-Q4_K_M. For 8 GB: Qwen3-7B-Instruct-Q4_K_M.
AI Models → OpenAI → API key + model ID (e.g. gpt-4o, gpt-5, o1-mini).
KnowSora automatically detects whether the model needs max_completion_tokens instead of max_tokens (gpt-5, o1, o3, o4) and whether temperature is supported. On API errors, it retries twice with the appropriate fields before giving up.
AI Models → Claude → API key + model ID (e.g. claude-sonnet-4-5, claude-opus-4-7).
AI Models → Gemini → API key + model ID (gemini-2.5-pro, gemini-2.5-flash, gemini-3-pro-preview).
Note: Preview models are often only enabled for certain accounts/regions. On 503 or "not found," switch to a stable model.
AI Models → xAI Grok → API key (generate at console.x.ai). Model suggestions:
grok-4.3 (default) — fast + cheap, good tool callsgrok-4.20 — flagship reasoninggrok-imagine-image-quality — image generation (callable from themedia generator via provider="grok")
grok-imagine-video — video generation (via media generator)Auth: xAI has no OAuth process for the API. Not even with X Premium / SuperGrok subscription. API key only. Credits must be topped up explicitly, there's no free tier.
AI Models → OpenRouter → API key (generate at openrouter.ai, Google/GitHub login possible, no card required for the free tier).
What makes OpenRouter special: One API key, access to 300+ models from OpenAI, Anthropic, Google, xAI, Meta, DeepSeek, Mistral and many more — including dedicated free-tier models for completely free use (with rate limit).
Recommended model choice:
openrouter/free (default, killer feature) — smart routing, picksthe best free model at runtime based on the active skill: - Coding assistant → Qwen3 Coder (1M context) - Knowledge research → Llama 3.3 70B / DeepSeek V4 / Gemini 2.0 Flash - Data analyst → DeepSeek V4 Flash (reasoning) - Web research → Llama 3.3 / DeepSeek - Media generator → first general-purpose free model with tool support - Coder models are automatically excluded for non-coding skills (anti_hints in the heuristic)
qwen/qwen3-coder:free — top coding model, 1M contextdeepseek/deepseek-v4-flash:free — reasoning, 1M contextmeta-llama/llama-3.3-70b-instruct:free — solid all-purposegoogle/gemini-2.0-flash-exp:free — multimodal, fastopenai/gpt-5 — paid, if credits are topped upanthropic/claude-sonnet-4.6 — paidPer-skill override: You can pin a preferred free model per skill under Skills → Edit → "🌐 Preferred OpenRouter Free Model". Smart routing then uses this instead of the heuristic. Leave empty for auto-selection.
Rate limits (as of July 2026):
20 req/min
KnowSora features for OpenRouter:
openrouter/smart) — picks the best free model atruntime based on the active skill (capability scoring by tool support, context size, model family)
via /api/v1/models (1h cache)
purchased and consumed credits
KnowSora automatically tries up to 2 more free models (filters for tool-calling capable ones if a skill is active)
HTTP-Referer +X-Title headers so your usage appears on the OpenRouter leaderboards
Cache refresh if new free models are expected:
curl -X POST http://localhost:48823/api/openrouter/refresh-cache \
-H "Authorization: Bearer $YOUR_KNOWSORA_TOKEN"
For other hosts with OpenAI API format (Together, Groq, local APIs, etc.). AI Models → Custom → base URL + API key + model ID.
OpenRouter should be used via the dedicated provider (No. 6) — otherwise you'll miss out on free-tier detection, auto-fallback, and credits display.
Three CLIs installed locally in the backend container. Login via OAuth (free for Pro/Plus/Subscription users) or API key.
Subscription overview (no extra API costs):
| CLI | Subscription | Cost Model | |---|---|---| | Claude Code | Anthropic Pro/Team | 5-hour limits, max ~50 requests/5h | | Codex | ChatGPT Plus/Pro/Team | Included in the ChatGPT plan | | Gemini CLI | Google account (free) | 1000 requests/day with Gemini 2.5 Flash for free |
OAuth login workflow (all three the same):
in the browser, paste the code back into the terminal
After successful login: KnowSora automatically detects the OAuth token periodically (every 2 sec) and closes the login window.
Important — CLI in container mode: The backend container automatically inserts a passwd entry for the container UID on start. Otherwise Node-based CLIs (especially Gemini's FileKeychain) crash with uv_os_get_passwd returned ENOENT. If there are issues: restart the backend, then ensure_passwd_entry() runs again.
---
A skill = system prompt + tool whitelist. Selectable in the skill pill at chat start. When a skill is active, the AI runs in a tool loop and can call the enabled tools.
Included default skills:
| Skill | Tools | Purpose | |---|---|---| | 🪄 Knowledge Research | search_knowledge_base, read_document, list_documents | RAG answers with source citation | | 🌐 Web Research | web_search, url_fetch, datetime_tool | DuckDuckGo + read web pages | | 📊 Data Analyst | list_documents, query_table, calculate | Analyze CSV/XLSX via SQL | | 💻 Coding Assistant | project_, python_exec, regex_test, json_tool, datetime_tool, http_request, url_fetch, web_search | Write/test code, work in the project folder | | 🤖 Claude Code | project_, all coding tools + KB search | Senior engineer persona with project tools | | 🎨 Media Generator | generate_image, edit_image, generate_video | Images/videos via OpenAI + Google + Grok | | 📄 Document Generator | generate_document, convert_document | Quotes/invoices/reports as PDF+Word, PDF↔Word conversion |
Skills are editable (Admin → Skills) — system prompt + tool list + icon + color per skill. "Reset to default" available per skill. New skills from updates are added incrementally, your customizations are preserved.
search_knowledge_base — Hybrid search (BM25 + vector with Reciprocal Rank Fusion) in a KB. Returns top-10 chunks with source metadata (max 20). Reliably finds proper names and exact terms too, not just semantically similar content.
read_document / list_documents — Load full text of a document / list files in a KB.
query_table — DuckDB SQL over CSV/XLSX documents in a KB. Ideal for "How many orders last month?" or aggregations.
calculate — Python math expressions (sandboxed).
python_exec — Full Python sandbox in its own subprocess, with output file capture. Files created under /tmp/ automatically appear as a download button in the chat.
web_search / url_fetch — DuckDuckGo search + read web pages as Markdown (BS4 + Readability).
http_request — Arbitrary HTTP calls (GET/POST/PUT/DELETE) with headers + body. Whitelist against internal IPs.
json_tool / regex_test / datetime_tool — Helper functions for parsing, pattern matching, time calculations.
These tools allow any AI (also OpenAI/Claude/Gemini API, not just CLIs) to work directly in the current project directory:
project_list_dir — Show directory contents (max 500 entries, optionally recursive).
project_read_file — Read file up to 1 MB (optional max_lines). No write permissions needed.
project_write_file — Write or append to a file, max 5 MB. Directories are auto-created. Requires enabled write permissions in the project.
project_delete — Delete file or directory (recursive for folders). Requires enabled write permissions.
project_run_shell — Run a shell command in the project's cwd (npm install, python -m pytest, git status, etc.). Hard timeout 90 sec (PROJECT_SHELL_TIMEOUT changeable), stdout/stderr capped at 50 KB. Requires enabled write permissions.
Write permissions toggle per project — same logic as for the CLIs:
message, list/read still work
Cross-provider workflow: This lets GPT-5 check what Claude Code just built in the project, or Gemini review a Python script created by Claude for bugs.
generate_image — AI image generation. Parameter provider:
openai (default) — gpt-image-1, photorealistic, possibly org verificationgemini — imagen-4.0-generate-001, strong artistic qualitygrok — grok-imagine-image-quality, photorealistic + strong text renderingedit_image — Image-to-image based on an uploaded photo. Automatically uses the first image attachment of the current message (attachment_index=0). JPEGs are converted to PNG server-side (Pillow) before being sent to OpenAI's /images/edits endpoint.
openai — gpt-image-1 edits, photorealisticgemini — gemini-2.5-flash-image (Nano Banana), creativegrok — grok-imagine-image-quality edits, base64 data URI as inputgenerate_video — Video generation. Parameter provider:
gemini (default) — Google Veo 3, 4-8 sec, approx. 1-3 min wait timegrok — grok-imagine-video, 5-15 sec, ~30 sec wait timeOpenAI and Anthropic have no public video API. Sora is only available in ChatGPT, not via API.
Three storage locations in parallel:
/data/media/<user_id>/<YYYY-MM-DD>/<uuid>.png # Original storage
/data/uploads/... # Provider output files
<project>/outputs/<YYYY-MM-DD>/<original_name> # If chat has a project
Every generated file is persisted as a ChatOutput DB row — remains visible in the chat history after tab switch and browser restart, with inline preview (images: <img>, videos: <video controls>).
If the chat has a project assigned:
All outputs are additionally automatically saved as a copy in the project directory (<project>/outputs/<date>/). On name conflicts, a counter is appended (image.png → image (1).png).
The project copy is completely independent of the DB: deleting the chat, deleting the ChatOutput, deleting the original in /data/media/ — none of this affects the project copy. Only deleting the project itself (or manually via the file browser) removes it.
Copy retroactively: In the chat header, there's a button "→ Project" (appears only if a project is assigned). This copies all existing outputs of the chat into the project at once — useful for outputs that were created BEFORE the project assignment.
---
uses this KB (provided the skill includes search_knowledge_base)
Supported formats: PDF, DOCX, XLSX, CSV, TXT, MD, HTML, JSON, PPTX.
Indexing: runs as a background worker. Status in the document listing (pending → processing → ready).
Embedding model: default nomic-embed-text-v1.5 (768-dim, ONNX, in-process via fastembed — no separate container). Switchable in the admin area under "Embed Models" (triggers auto-reembed of all documents).
---
Projects are persistent working directories per user under /data/projects/<user_id>/<slug>/. When activated as a project pill in the chat, the AI uses this directory as cwd for Claude Code, Codex, Gemini CLI and for the project_* tools (any provider).
Functions per project:
- Images (png/jpg/gif/webp/svg/avif/bmp): <img> inline - Videos (mp4/webm/mov/mkv/avi): <video controls> - Audio (mp3/wav/ogg/flac/m4a/aac): <audio controls> - PDF: <iframe> (75vh tall) - Text/code (50+ extensions — py/js/ts/json/md/csv/sql/yaml etc.): <pre> - Office/archive/binary: info box with download button
node_modules, .git, .venv, dist, build, __pycache__ etc.) - Default: hidden files excluded - ?include_hidden=true for backups including .env
only read: - Claude Code CLI: --permission-mode=default + disallowed tools - Codex CLI: --sandbox=read-only - Gemini CLI: --approval-mode=plan + allowed-tools whitelist - project_* tools: write/delete/shell → clear error message
Outputs subfolder: <project>/outputs/<date>/ contains all images/videos/files generated by the chat in skill mode. Automatically populated on creation, can also be filled retroactively for existing outputs via a button in the chat header.
---
For SSH access to the NAS directly from the browser. Log in with the SSH credentials of the NAS user.
Configuration: Admin → Web Shell → set SSH host, port, and default user. NAS-specific SSH port (e.g. 50199 instead of 22) is supported.
---
While Claude Code, Codex, or Gemini CLI are actively working, the user sees live what the CLI is doing:
● 📖 Reading src/auth.ts
✏️ Editing schema.prisma
🔧 Running `npm install`
✅ Command completed
Works in all skill and classic modes. The box appears automatically when the job runs, disappears after done. Current action is highlighted with a pulsing dot.
---
When a provider creates a file in the container and mentions a path in the response, a download button automatically appears under the assistant bubble.
Detection via:
{{download:/tmp/foo.html}} or[[download:/tmp/foo.html]]
reference an allowed file
Allowed source directories: /tmp/, /var/tmp/, /data/.claude_home/, /data/outputs/, /home/, /data/media/
System directories (/etc/, /usr/, /proc/, /root/, /var/log/) are ignored.
Limits: 50 MB per file, max 10 files per response, ~50 file extensions on the whitelist.
---
Since May 2026, KnowSora supports speech-to-text (STT) and text-to-speech (TTS) with three selectable engines.
| Engine | STT | TTS | Cost | Latency | Requirement | |--------|-----|-----|--------|--------|---------------| | Browser (default) | Web Speech API | Web Speech API | free | <500ms | Chrome/Safari/Edge | | OpenAI | Whisper-1 | TTS-1 (MP3 streaming) | ~0.006 USD/min STT, ~0.015 USD/1k chars TTS | 1-2s | OpenAI provider with API key | | Gemini | gemini-2.5-flash | gemini-3.1-flash-tts-preview | free tier | 3-8s | Gemini provider with API key |
Browser engine is the default choice and works best on iPhone/iPad with Apple voices. Limited under Linux/Firefox — switch to OpenAI or Gemini there.
Voice is always built in — no backend configuration needed. A microphone icon appears in the chat input line. Voice engine + voice + language can be set per browser profile in the Voice Settings popover (gear/⋮ menu).
Important: Browsers only allow microphone access over HTTPS or localhost. For self-host setups with a reverse proxy, be sure to set up Let's Encrypt (see "Reverse Proxy" section), otherwise voice input won't work.
KnowSora has a built-in dictionary that automatically correctly pronounces ~50 tech abbreviations:
longer texts and plays them one after another)
context — the first speaker tap per tab must be manual
---
Since May 2026, search_knowledge_base uses hybrid retrieval instead of pure vector search. No configuration needed — always active.
Each search runs in parallel across two engines:
content, even with synonyms and paraphrases
proper names, IDs, codes
The two ranked lists are fused into a single top list using Reciprocal Rank Fusion (RRF). Tokenization: lowercase, German + English stopwords removed, minimum length 2 characters.
The BM25 index is kept in memory per KB filter and automatically invalidated on:
The first search call after cache invalidation rebuilds the index (<100ms for 10k chunks).
Additionally, the filename is included as a header in the embed text: [Source: filename.pdf]\n\n<chunk>. This means terms that only appear in the filename are embedded too and can be found via vector search for the correct file.
The stored chunk text remains original — users don't see the prefix in the research results.
Important: Documents uploaded BEFORE May 2026 don't have the filename prefix. Via KB Edit → "🔄 Re-index N existing documents", all docs in a KB can be reprocessed with current settings.
---
Optional, activatable per KB. Based on the Anthropic method anthropic.com/news/contextual-retrieval.
During indexing, a 1-2 sentence context is generated per chunk via LLM from the overall document and prepended to the chunk — but only for embedding. The stored chunk remains original.
Example:
Chunk: "The result was 5.2 million EUR in Q3."
Embed input (with contextual): "This chunk is from the Q3 2024 report of ACME GmbH and describes the quarterly revenue. The result was 5.2 million EUR in Q3."
Result: hit relevance improved by 30-50% for questions like "What was ACME's revenue in Q3?".
Open KB → Edit → enable "🧠 Contextual Retrieval (smart chunking)" → choose LLM provider → save.
Provider choice per KB:
Via the "🔄 Re-index N existing documents" button in the KB Edit modal. All docs are set to pending and reprocessed by the worker — this time with contextual retrieval.
5s → 15s → 30s → 60s (max 5 attempts per chunk)
DOC_PROCESS_TIMEOUT_MAX in .env
| Doc size | Time with contextual (OpenRouter free) | |-----------|---------------------------------------| | 5 chunks | ~30-40s | | 20 chunks | ~2-3 min | | 100 chunks | ~10-15 min | | 500 chunks | ~45-60 min |
With OpenAI gpt-4o-mini, about 5-10x faster.
---
KnowSora is designed responsively and uses an adapted layout under 768px viewport width:
Collapsible header. On mobile, the selector row (Provider/Skill/Project/KB) and the attachment bar are collapsed by default — only the title and action buttons are visible. Maximum room for the chat. The chevron button next to "Clear" toggles the options on and off.
Mobile-safe selectors. Provider/Skill/Project/KB dropdowns use min(280-360px, calc(100vw - 24px)) as width with maxHeight: 70vh and scroll — no overflowing at the screen edge.
Admin user list as cards. Instead of the desktop table, on mobile a compact card appears per user with avatar, username, email, role/status badges, and edit/delete buttons in a row.
Provider cards stacked. Long provider names (e.g. "OpenAI (GPT-4o, etc.)") aren't truncated — on mobile the title and config buttons appear one below the other instead of side by side.
AI Models menu visible only to admins. End-customer users don't see the entry. They select providers directly in the chat dropdown — the list shows only providers with usable=true (API key present or OAuth active). API keys are never returned to the frontend.
Voice on mobile. Microphone icon remains prominent in the main row. Other voice options (auto read-aloud, voice settings, project attachment) are in the overflow menu (⋮) so the input field stays large. Voice settings as a bottom sheet with large touch targets instead of a popover.
---
If KnowSora runs behind NPM or another proxy, the following timeouts must be increased, otherwise long AI requests get killed:
NPM → Proxy Host → Advanced → Custom Nginx Configuration:
# Timeouts for long AI/CLI requests (up to 1 hour)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 60s;
# Uploads up to 200 MB
client_max_body_size 200M;
# WebSocket for CLI login terminal
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Standard headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Don't buffer streaming
proxy_buffering off;
proxy_cache off;
Frontend polling logic automatically does exponential backoff (800ms → 1.6s → 3.2s → 6.4s → max 8s) on repeated network errors, so the browser console doesn't spam during Let's Encrypt cert renewals.
---
Render errors in the frontend are caught — instead of a blank screen you see a fallback UI with:
<details>Global window.error + unhandledrejection listeners also log to localStorage["knowsora-errors"] (last 30 events) for post-mortem diagnosis.
On OpenAI 400 errors:
temperaturethe field switched
On Gemini 400:
Clear translation of 404/503 errors into user-friendly messages with a hint on which model works instead.
Chat requests run as a background job:
/api/chat/job/{id} every 800ms → reads status +activity events live
localStorage["kh-job-<chat_id>"] → closingthe tab and reopening it reconnects seamlessly
Fixed in this version: user messages are shown optimistically with id: Date.now(). The real backend ID is patched back into the state via .then(). On very fast edits right after sending, there's a 404 fallback that re-saves the message and continues locally.
On JWT expiration (HTTP 401), it doesn't navigate hard via window.location.href — instead a custom event knowsora-auth-expired → React Router does the redirect to /login without a page reload (no temporary blank screen).
---
| Endpoint | Method | Purpose | |---|---|---| | /api/auth/login | POST | Login (form-encoded), returns JWT | | /api/auth/me | GET | Current user | | /api/chats/ | GET/POST | Chat list / create new chat | | /api/chats/{id} | PUT/DELETE | Change chat / delete | | /api/chats/{id}/messages | GET | Load messages of a chat | | /api/chats/{id}/messages/{msg_id} | PUT | Edit user message + delete subsequent ones | | /api/chats/{id}/copy-outputs-to-project | POST | Copy existing chat outputs into the project | | /api/chat/start | POST | Start an AI request as a background job | | /api/chat/job/{job_id} | GET | Poll job status + activity events | | /api/chat/job/{job_id}/cancel | POST | Cancel a running job | | /api/skills/ | GET / POST / PUT / DELETE | Skill management | | /api/skills/tools | GET | Tool catalog | | /api/knowledge-bases/ | GET/POST/PUT/DELETE | KB management | | /api/documents/ | GET/POST | Document list / upload | | /api/projects/ | GET/POST | Project list / create | | /api/projects/{id}/browse | GET | Directory contents | | /api/projects/{id}/file | GET/DELETE | Read / delete file, ?download=1 for FileResponse | | /api/projects/{id}/upload | POST | Upload a single file | | /api/projects/{id}/upload-zip | POST | Upload + extract ZIP archive | | /api/projects/{id}/download-zip | GET | Download the whole project as ZIP | | /api/projects/{id}/mkdir | POST | Create directory | | /api/message-attachments/upload | POST | Upload image/file for the next message | | /api/message-attachments/{id} | DELETE | Delete attachment | | /api/chat-outputs/{id}/download | GET | Download provider-generated file | | /api/media/{user_id}/{path} | GET | AI-generated media (auth + owner check) | | /api/ai-providers/ | GET/POST/PUT/DELETE | Provider management | | /api/ai-providers/{id}/test | POST | Test call to provider | | /api/cli-login/start/{cli} | POST | Start OAuth login session | | /api/cli-login/ws/{cli} | WS | Terminal stream for CLI login | | /api/cli-login/status/{cli} | GET | Current OAuth status | | /api/embed-models/catalog | GET | Available embedding models | | /api/embed-models/activate | POST | Switch embedding model | | /api/admin/users | GET/POST/PUT/DELETE | User management |
JWT token as Authorization: Bearer <token> header.
---
┌──────────────┐
│ Nginx Proxy │
│ Manager │
│ (optional) │
└──────┬───────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼───────┐ ┌──────▼───────┐ ┌──────▼───────┐
│ Frontend │ │ Backend │ │ llama-chat │
│ Vite+React │ │ FastAPI │ │ llama.cpp │
│ Port 47822 │ │ Port 48823 │ │ Port 48824 │
└──────────────┘ └──────┬───────┘ └──────────────┘
│
┌─────────┼─────────┐
│ │ │
┌──────▼──┐ ┌────▼────┐ ┌──▼───────┐
│fastembed│ │ChromaDB │ │ DuckDB │
│ (ONNX, │ │persist │ │:memory: │
│in-proc) │ │ │ │query_tbl │
└─────────┘ └─────────┘ └──────────┘
│
┌───────┴────────┐
│ SQLite │
│ /data/ │
│ knowsora.db │
└────────────────┘
All containers in host network mode — no port mapping, direct LAN access.
Data persistence — everything under /data/ (or the path from DATA_DIR):
knowsora.db — main DBchroma/ — vector storemodels/ — GGUF filesuploads/ — chat attachments + provider output filesmedia/<user>/<date>/ — AI images/videos (persistent independent of chat)projects/<user>/<slug>/ — coding projectsprojects/<user>/<slug>/outputs/<date>/ — automatic backup copies of all chat outputs for this chat.claude_home/, .codex/, .gemini/ — CLI OAuth tokensBackup recommendation: Back up the entire /data path PLUS the .env (contains SECRET_KEY) PLUS /data/.secret_key (persistence copy of the key). When restoring, the SECRET_KEY must be identical to the original installation, otherwise encrypted API keys and sessions become unusable.
---
Backend won't start, logs show "permission denied" on /data: HOST_UID/HOST_GID in .env don't match the volume owner. Fix:
chown -R 1000:1000 /volume3/docker/knowsora/data
Gemini CLI: "uv_os_get_passwd returned ENOENT": Container UID has no passwd entry. Should be automatically fixed at backend start. If not: docker compose down && docker compose up -d --build (no --no-cache needed — Dockerfile has chmod 666 /etc/passwd /etc/group).
Gemini CLI: "folder is not trusted": Automatic entry in trustedFolders.json fails. Check logs. Workaround: restart the backend container — on the next tool call the entry is rewritten.
OpenAI: "max_tokens is not supported, use max_completion_tokens": Should be automatically retried. If it persists: AI Models → OpenAI → check which model is configured. For gpt-5, o1, o3, o4, max_completion_tokens is mandatory.
Veo video: "Model not found / 503":
veo-3.0-generate-001 (premium) orveo-3.0-fast-generate-001 (cheaper) or veo-2.0-generate-001 (fallback)
model: "veo-2.0-generate-001" as overrideImages/videos disappear after tab switch: Shouldn't happen anymore with the current version — media outputs are persisted as ChatOutput DB rows. If it still happens: old images (from before the update) aren't migrated; new generations remain.
Project tools "No project assigned": For the project_* tools, a project must be explicitly selected in the project pill at the top of the chat. Switching to a chat without a project disables the tools (they return clear error messages).
Project_run_shell fails with "write permissions disabled": Enable "write permissions" in the project editor. Same toggle as for the CLI providers (Claude Code, Codex, Gemini CLI).
Codex CLI: "bwrap: No permissions to create a new namespace": Host kernel has kernel.unprivileged_userns_clone=0. Typical for UGREEN, Synology, some embedded Linuxes. manage.sh update sets this automatically via CODEX_SANDBOX_MODE=danger-full-access in .env. If detection doesn't kick in, manually:
echo "CODEX_SANDBOX_MODE=danger-full-access" >> .env
docker compose up -d --force-recreate backend
docker exec knowsora_backend env | grep CODEX_SANDBOX_MODE
Codex CLI: "Reading additional input from stdin..." (exit 1): Fixed in the current version — Codex' argument parser can get confused by leading -/-- in the prompt and falls into interactive stdin mode. Fix: -- as a separator before the prompt (codex exec ... -- "<prompt>").
Updated but frontend shows the old version:
docker exec knowsora_frontend sh -c 'ls /usr/share/nginx/html/assets/index-*.js'
Hash should change after update.
unzip -o knowsora_release.zip && ./manage.sh update
reopen or iOS → Safari → Advanced → Clear Website Data
Backend logs:
./manage.sh logs # all containers, live
docker logs -f knowsora_backend
docker logs -f knowsora_frontend
Filter in the backend log: [worker], Job <id>, Skill '<name>', OpenAI, Gemini, Claude, Grok, project_.
---
KnowSora can connect to Atlassian Jira and Confluence — entirely via the web GUI, without any Docker/.env intervention and without Atlassian admin rights. Both use the same API token and access read-only — nothing is changed or deleted in Atlassian.
Two usage modes:
jira_search (JQL), confluence_search (wiki full-text) and confluence_get_page. Enable in a skill (Admin → Skills → Tools), then the AI searches Jira/Confluence live.
a Jira project (by project key, e.g. HOTSPOT) or a Confluence space (by space key, e.g. ENZY from the URL …/wiki/spaces/ENZY/…). The content is periodically indexed into a knowledge base and then searchable via the normal knowledge search.
Setup (one-time, by a KnowSora admin):
without admin rights: https://id.atlassian.com/manage-profile/security/api-tokens → "Create API token". The same token applies to Jira and Confluence.
enter site URL (e.g. https://frederix-hotspot.atlassian.net), account email and API token.
The token is stored encrypted in the database (no plaintext) and serves as a shared read access for all users of the instance.
Usage (by any user):
(Jira project or Confluence space), specify key and target knowledge base, choose interval (manual up to weekly). Refresh button triggers a sync immediately.
about tickets or wiki content.
Notes:
integration is focused on Atlassian.
May 1, 2025. KnowSora uses the current /rest/api/3/search/jql endpoint with token pagination.
KnowSora is developed and sold by localeye.shop. Licensing questions, support, custom adaptations: https://localeye.shop or email the provider.
---
New menu item Memory (🧠) — permanent facts about the user that are automatically included as context in every chat and with every AI model (provider/model-switch-independent).
After each saved AI response, the backend checks whether at least 6 new messages have been added to this chat since the last check (FACTS_TRIGGER_MSGS). If so, the new conversation excerpt is sent to an LLM which extracts permanent facts from it and assigns a category: fact / preference / project / person. Progress is tracked per chat (chat.facts_upto_msg_id) so the same section isn't analyzed repeatedly.
The running auto-trigger only covers the currently open chat. Older or very short chats (that never crossed the 6-message threshold) are never captured. The Autosync button on the Memory page catches this up retroactively across all of the user's chats:
backend request) so a single request doesn't run indefinitely long. The frontend automatically calls the endpoint again in multiple rounds until complete: true is returned — no manual multiple clicking needed.
searched, Y facts total") and the current round, and is visually clearly recognizable as active (filled "primary" button instead of a hard-to-recognize default state).
chat.facts_upto_msg_id — arepeated click at a later time only searches what has been newly added since then.
Facts are grouped by category on the page with a colored section heading (fact=gray, preference=blue, project=green, person=orange) instead of a single, uniform-looking list. Each entry additionally shows via icon whether it was created automatically (✨) or manually (👤), and can be edited, hidden ("soft forget") or deleted.
Facts that were automatically extracted before this feature expansion were all hard-set to the category "fact". The Recategorize button (appears once at least one fact exists) sends all existing facts to the LLM once and reassigns the appropriate category to each — repeatable as often as needed, without duplicate processing.
The delete button has two states: if facts exist, it deletes all of them and simultaneously resets the sync progress of all chats (facts_upto_msg_id = NULL) so Autosync starts from zero again afterward. If no facts exist, the button remains visible (label "Reset sync") and only resets the progress pointers — without this, an empty memory with a far-advanced sync pointer would be a deadlock: Autosync would forever find 0 new messages, and there would be no way to fix this via the UI.
GET /api/user-memory/ — own facts (newest first)
POST /api/user-memory/ — create fact manually
PUT /api/user-memory/{id} — edit / (de)activate fact
DELETE /api/user-memory/{id} — delete fact
DELETE /api/user-memory/ — delete all own facts + reset sync pointer
POST /api/user-memory/sync — autosync (retroactive extraction across all chats)
POST /api/user-memory/recategorize — one-time recategorization run
Upper limit: max 80 facts per user (oldest must be manually deleted to make room), max 400 characters per fact.
---
New default skill Document Generator (generate_document, convert_document):
cover letters as PDF and/or Word (title, intro lines, sections with body text/bullets, line-item table with total row, footer).
between PDF and Word. pdf_to_word runs purely in Python (pdf2docx), word_to_pdf uses LibreOffice headless in the container.
additionally automatically writes a ready-made, copy-paste-ready email text (subject, salutation, body text, closing) directly into the response.
Requirement: The system package libreoffice-writer is installed in the image (Dockerfile ARG INSTALL_LIBREOFFICE=true). Without this package, word_to_pdf returns a clear error message instead of crashing; building with INSTALL_LIBREOFFICE=false saves about 350–450 MB of image size, then only pdf_to_word is available.
The chat now automatically scrolls smoothly downward during a running AI response — no more manual scrolling needed. If the user scrolls up themselves in the meantime, auto-scroll pauses (no yanking out of the history) and a "Jump to end" button appears at the bottom right.
The database now runs in WAL journal mode with busy_timeout=30000 instead of the standard DELETE mode. Reduces sporadic "database is locked" errors with multiple simultaneous background jobs (polling, streaming, attachments).
Problem with SQLite: SQLite serializes write access — even with WAL mode (see above), it remains the bottleneck once many employees are chatting simultaneously, uploading documents, or editing KBs (heavy parallel write access). For individual users up to small teams, SQLite remains completely sufficient and remains the default — nothing changes for existing installations as long as nothing is configured.
Solution: KnowSora now optionally supports PostgreSQL as a database backend. Postgres uses row-level instead of DB-wide locking and thus scales significantly further for parallel multi-user load. The switch is purely configuration-based — the SQLAlchemy models remain unchanged and DB-agnostic.
Existing installation → automatic (default case, no manual entry needed): ./manage.sh update checks on every run whether the old SQLite file (${DATA_DIR}/data/knowsora.db) with real data still exists and DATABASE_URL doesn't yet point to Postgres. If so, it without asking:
openssl rand -hex 24).POSTGRES_PASSWORD, DATABASE_URL (user knowsora) andCOMPOSE_PROFILES=postgres into .env.
healthy.skills, KB metadata; sequences continue correctly) — before the backend starts against Postgres, so no duplicate admin user is created.
installation runs permanently on Postgres.
The generated Postgres password is then in .env under POSTGRES_PASSWORD (not the same as the web GUI admin login).
Manually enabling for a new installation (only relevant if Postgres is desired from the start instead of SQLite, without existing SQLite data):
.env: set POSTGRES_PASSWORD, enable COMPOSE_PROFILES=postgres,enter DATABASE_URL=postgresql+asyncpg://knowsora:<PASSWORD>@127.0.0.1:<POSTGRES_PORT>/knowsora (example lines already in .env.example).
./manage.sh updateDetails:
postgres container starts only if COMPOSE_PROFILES=postgresis set — without this line (or as long as no old SQLite installation with data is detected), everything remains on the SQLite default, no additional container, no risk to existing setups.
${DATA_DIR}/data/postgres.DB_POOL_SIZE/DB_MAX_OVERFLOW, default 10/20) isconfigurable in .env — higher for very many simultaneous users.
./manage.sh reembed / embed-reset work with Postgres the sameas with SQLite (use the normal DB engine internally, no more hard-wired SQLite access).
./manage.sh migrate-to-postgres remains available as a manual singlecommand (e.g. for a repeat/manual migration) — normally ./manage.sh update handles it automatically, see above.
checked freshly by ./manage.sh update on every run based on two things — COMPOSE_PROFILES=postgres active and no marker ${DATA_DIR}/data/.postgres_migrated present. If a previous update run had already gotten as far as switching .env to Postgres, but the actual data transfer was interrupted (crash, restart mid- deploy, etc.), the next update run automatically catches this up — the marker is only set after successful completion. The migration run itself is idempotent (clears the target tables before each (re-)import), so a retry doesn't lead to duplicate data.
POSTGRES_PASSWORD/DATABASE_URL are missing in an existing .enveven though COMPOSE_PROFILES=postgres is already set (e.g. incomplete manual setup), ./manage.sh update automatically adds them with a newly generated password — again without manual entry.
re-export data if needed.
---
In the active chat, the badge next to the AI provider selector now clearly shows which mode is being used:
Visible directly next to the provider pill in the top left of the chat — without having to go into settings. Applies to Claude Code, Codex, and Gemini CLI in all auth modes.
On the AI Models page (admin area), a billing status line also appears per CLI provider: "OAuth active (free)" or "API key active (paid)".
Live update on automatic fallback (auth mode "Automatic"): If, during a running chat, the OAuth session limit is reached (or the token has expired/is invalid), KnowSora automatically switches to the stored API key mid-response — and the badge jumps from "Free" to "Token" at that same moment, without reload or chat switch. The backend reports actual_billing per response (what was actually used), the frontend immediately adopts this value into the badge. Only affects the "Automatic" auth mode — with "OAuth only"/"API key only", the badge naturally stays fixed, since no fallback occurs there.
When an OpenRouter model has reached its daily limit (HTTP 429), a comprehensible message now appears instead of raw JSON:
OpenRouter limit reached (429): This model is exhausted for today.
KnowSora is automatically trying a fallback model …
KnowSora then automatically tries up to 2 more free models (with tool-calling support if a skill is active).
If Codex fails with "bwrap: No permissions to create a new namespace", KnowSora automatically retries the call with --sandbox danger-full-access — without a restart and without manual .env changes. The previous manage.sh mechanism (CODEX_SANDBOX_MODE) remains as a persistent default setting, the auto-retry additionally catches all runtime exceptions.
Provider error messages (rate limits, quotas, auth errors) are now generally cleaned up before being shown to the user. No more raw JSON body in the chat UI — only the relevant core information.
When switching to another chat, open "insert text" snippets (pasteBlocks) are automatically reset. No more accidental sending of text blocks copied from another chat.
Backend and frontend now consistently show message timestamps in the browser's local time zone. The fix affects chat history and project file lists.
---
Selectable in the AI provider settings:
automatic fallback on error/limit.
authorization for Codex".
localhost:1455) — this is normal.?code=…) intoKnowSora's "Paste error page address" field → "Complete".
docker exec knowsora_backend ls -la /data/.claude_home/.codex/auth.jsonAI Provider → Claude Code → "Log in again" → /login in the terminal → log in with the URL in the browser → paste the code back.
On "exit 1" or hanging calls: "Repair" button at the respective CLI provider. Clears session/cache, token is preserved.
Complete auth documentation & changelog: see CHANGELOG-AUTH.md.
Installation on any system — adjust ZIP names, port, and IP examples below for each product.
DSM → Package Center → search for Container Manager and install.
DSM → Control Panel → Terminal & SNMP → enable SSH.
ssh admin@SYNOLOGY-IP
scp productname_release.zip admin@SYNOLOGY-IP:/volume1/docker/
ssh admin@SYNOLOGY-IP
cd /volume1/docker
unzip productname_release.zip -d productname
cd productname
cp .env.example .env
./manage.sh update
WebUI: http://SYNOLOGY-IP:PORT — Login: admin / admin
💡 Synology DSM 7.2+ with Container Manager 20.10+ recommended. Adjust the path /volume1/docker/ if needed.
Price plus 19% VAT. One-time payment, no subscription costs, self-hosted.