A Kotlin Multiplatform application for Android, Desktop, CLI, and Web, with cloud services and an administration panel.
For build, test, and independent service setup instructions, see DEVELOPMENT.md.
Download the latest alpha build
After a filesystem or Lucene service restart, later requests reconnect automatically. An interrupted request still reports failure; failed operations and partial uploads are not replayed automatically.
Worker task settings are stored per task type. A task without a persisted configuration does not run.
The CLI add command accepts a taskConfig preset whose taskConfigData entries contain type,
isEnabled, fetchSize, and waitDurationMillis; see deploy/dev-data/7_preset_task_config.json.
Administrators can edit the same values from the Panel's Worker task configurations page. Workers
reload the setting before every iteration, so enabling, disabling, or tuning a task does not require a restart.
When the persisted TOPIC_MODERATION task configuration is enabled, the worker loads the active LLM
configuration from backend storage. Apply one of the cloud/worker/llm-config.*-example.json presets with
the Cloud CLI add command. For OpenRouter, set a valid API key and model identifier in
cloud/worker/llm-config.openrouter-example.json; its official Koog client uses OpenRouter's default API
endpoint and ignores baseUrl. For LiteRT-LM, set modelPath in
cloud/worker/llm-config.litert-example.json to an existing local .litertlm model before applying it;
the worker does not download the model.
All providers return the same structured moderation decision. The LiteRT-LM client uses native JSON-schema
constrained output, tries the GPU backend first, and falls back to CPU if GPU initialization fails. Set
cachePath in the LiteRT-LM preset to a directory writable by the worker. Existing configurations without
cachePath use a model-specific directory under the JVM temporary directory.
LiteRT-LM's prebuilt Linux x86_64 library requires AVX. If the host cannot run the selected provider,
disable the persisted TOPIC_MODERATION task configuration; topic content is then not reviewed and
harmful-topic authors are not automatically marked READ_ONLY.
Moderation covers topics and comments in communities, user spaces, and public rooms that belong to a
community. Encrypted private and notification-room topics are excluded. Harmful content such as abusive
profanity, threats or graphic violence, pornography, hate, self-harm, exploitation, or promoted illegal
activity causes the author account to be marked READ_ONLY.
Windows GPU inference requires 64-bit dxil.dll and dxcompiler.dll from Microsoft's
DirectX Shader Compiler. The
com.google.ai.edge.litertlm:litertlm-jvm artifact does not include these DLLs. LiteRT-LM loads them
through the native Direct3D 12 runtime, so adding their directory to PATH alone is not sufficient for
the current worker. Put both DLLs beside the java.exe that actually starts the worker.
The following PowerShell example installs the DXC version currently pinned for worker deployment. Use an
application-owned Java 21 runtime when possible instead of modifying a shared system JDK. Set
JAVA_HOME to that runtime before running these commands.
$dxcArchive = Join-Path $PWD "dxc_2026_07_29.zip"
$dxcDirectory = Join-Path $PWD "dxc-v1.9.2607"
$expectedDxcHash = "A1DFB116BA3EEAE6A1582291B53A8E7BF65AD760676BD3194685C8F7367CD241"
if ([string]::IsNullOrWhiteSpace($env:JAVA_HOME)) {
throw "Set JAVA_HOME to the Java 21 runtime used by the worker"
}
$workerJavaDirectory = Join-Path $env:JAVA_HOME "bin"
$workerJavaExecutable = Join-Path $workerJavaDirectory "java.exe"
$dxilDestination = Join-Path $workerJavaDirectory "dxil.dll"
$dxCompilerDestination = Join-Path $workerJavaDirectory "dxcompiler.dll"
if (-not (Test-Path -LiteralPath $workerJavaExecutable)) {
throw "Worker Java executable not found: $workerJavaExecutable"
}
if (Test-Path -LiteralPath $dxcDirectory) {
throw "DXC extraction directory already exists: $dxcDirectory"
}
if ((Test-Path -LiteralPath $dxilDestination) -or (Test-Path -LiteralPath $dxCompilerDestination)) {
throw "DXC DLLs already exist in $workerJavaDirectory; verify or back them up before replacing them"
}
Invoke-WebRequest `
-Uri "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.9.2607/dxc_2026_07_29.zip" `
-OutFile $dxcArchive
$actualDxcHash = (Get-FileHash -LiteralPath $dxcArchive -Algorithm SHA256).Hash
if ($actualDxcHash -ne $expectedDxcHash) {
throw "DXC SHA-256 mismatch: $actualDxcHash"
}
Expand-Archive -LiteralPath $dxcArchive -DestinationPath $dxcDirectory
Copy-Item -LiteralPath (Join-Path $dxcDirectory "bin\x64\dxil.dll") -Destination $dxilDestination
Copy-Item -LiteralPath (Join-Path $dxcDirectory "bin\x64\dxcompiler.dll") -Destination $dxCompilerDestination
Get-Item -LiteralPath $workerJavaExecutable, $dxilDestination, $dxCompilerDestinationRestart the worker after installing the DLLs. A successful GPU startup logs both messages:
Initializing LiteRT LLM client with GPU backend
Initialized LiteRT LLM client with GPU backend
If GPU initialization fails, the worker remains available and logs the fallback before initializing a new CPU engine:
LiteRT GPU initialization failed; falling back to CPU
Initialized LiteRT LLM client with CPU backend
A warning about missing libLiteRtTopKWebGpuSampler.dll concerns LiteRT-LM's optional WebGPU sampler.
Version 0.16.1 does not ship that DLL in its JVM artifact and uses its built-in C sampler instead; this
warning does not by itself mean that the main model fell back from GPU. Use the backend initialization
messages above as the source of truth.
- Java 21 (Eclipse Temurin)
- The included Gradle wrapper (
./gradlew) - Git Bash on Windows for repository shell scripts
- Docker with Compose support for the local service stack
- Desktop AsciiDoc previews use the system WebView engine: WebView2 on Windows, WKWebView on macOS, and WebKitGTK on Linux.
# Assemble included modules
./gradlew assemble -Pserver.flavor=sample -Pserver.buildType=prod
# Build the Cloud server
./gradlew :cloud:server:assemble -Pserver.flavor=sample -Pserver.buildType=prod
# Build the Android app
./gradlew :app:androidApp:assembleDebug -Pserver.flavor=sample -Pserver.buildType=prod
# Build the Desktop app
./gradlew :app:desktopApp:packageDistributionForCurrentOS -Pserver.flavor=sample -Pserver.buildType=prodAdd app to a flavor's COMPOSE_FILE_LIST to deploy the user app and admin panel as Wasm sites.
Compose builds both sites in the JDK 21 builder stage of wasm.Dockerfile, passes only the required
flavor, build type, and server URLs as build arguments, and copies each distribution into its own
Nginx runtime image. Deployment compiles backend and Wasm distributions inside Docker; the host
does not need a local JDK or prebuilt distributions. Images are built sequentially before startup:
./scripts/service_scripts/start-service-in-docker.sh sampleTo refresh configuration using existing Docker-built images, run ./scripts/service_scripts/start-service-in-docker.sh sample --no-build.
Open the user app at http://localhost:8080 and the panel at http://localhost:8081 after the containers become healthy. Omit app to skip both Wasm builds and services.
Both compose-service.sh and start-service-in-docker.sh accept an environment name
(alpha or alpha.env) or a file path (~/deploy/alpha.env). Names resolve to
~/deploy/<name>.env first, then deploy/<name>.env. Use deploy/sample.env as the reference;
BUILD_TYPE is required, and COMPOSE_FILE_LIST selects which services are included in the stack.
Server, Worker, WebSocket server, and CLI images share the JDK 21 builder in the root Dockerfile and
are packaged as the separate server, worker, ws, and cli targets.
When COMPOSE_FILE_LIST includes bunker, copy deploy/bunker.env.example to
~/deploy/bunker.env outside the project and replace its example domains and credentials.
Set BUNKER_ENV_FILE=/home/<user>/deploy/bunker.env inside the selected application env file
to use another path; a relative path is resolved beside that env file. A shell override takes precedence. The file is injected only into the BunkerWeb
container using env_file; it does not participate in Compose interpolation or override the flavor
configuration. Docker Compose 2.30 or newer is required for the raw env-file format, which preserves
literal credential values without interpolation. It contains final BunkerWeb names such as SERVER_NAME, ADMIN_USERNAME,
ADMIN_PASSWORD, and api.example.com_REVERSE_PROXY_HOST, rather than the former BUNKER_* variables.
For filesystem storage, set FILESYSTEM_PUBLIC_URL to the externally reachable media origin
(for example https://media.example.com), and FILESYSTEM_EXPOSE_PORT to the published HTTP port
(default 8822). Clients download /objects/<bucket>/<name> directly from this service.
With BunkerWeb, the media domain routes this path to filesystem port 8822; port 8820 is the internal
management RPC endpoint and must remain private.
Set FILESYSTEM_PUBLIC_URL=https://<media-domain> in deploy/{flavor}.env, including BunkerWeb deployments.
The startup script passes the same resolved origin to filesystem and the application services.
PRESET_PATH=/home/<user>/data bind-mounts the host preset directory into Server and CLI
as /app/deploy/preset_data. CLI reads numbered JSON presets and referenced local files there.
Enable CLI_INIT_ENABLE=true only for deliberate data initialization; disable it afterwards.
This is a private project. All rights reserved.