Website · Releases · Contributing
Souz is a Kotlin Multiplatform AI assistant focused on safe, observable, user-approved automation. It combines a Compose Desktop app, a reusable graph-based agent runtime, shared backend-safe tools, local and cloud LLM providers, sandbox-aware file/process access, and an HTTP backend for web/API integrations.
The project is designed around one core idea: an AI agent should be useful enough to operate your desktop and data, but transparent and constrained enough that users can trust what it is doing.
- Kotlin Multiplatform app surfaces built with Compose for Desktop.
- Selectable graph agents: the default
GraphBasedAgentuses memory recall, direct-tool classification, compact Skill inventory, and MCP injection, whileSkillsGraphBasedAgentexposes only Skill/Knowledge core tools. - Shared runtime layer used by desktop and backend for LLM clients, settings/config, sandbox-aware filesystem access, and backend-safe tools.
- Sandbox abstraction for filesystem and command execution, with local mode by default and opt-in Docker-backed execution.
- HTTP backend with trusted-proxy auth, OpenAPI/Swagger docs, onboarding, per-user settings/provider keys, chat lifecycle, message execution, Telegram bot chat bindings, cancellation, option continuation, event replay, WebSocket streaming, and PostgreSQL persistence.
- Rich desktop tool catalog for files, browser, web search/research, config, notes, applications, data analytics, calendar, mail, text replacement, Telegram, desktop capture, and calculator.
- SafeMode confirmations for tool permission prompts, destructive Telegram operations, ambiguous contact/chat selection, and deferred file-modification review.
- Persistent memory with scoped facts, prompt recall, completed-turn capture, desktop SQLite storage, Memory UI, and optional local Dreamer maintenance.
- Multi-provider LLM support for GigaChat, Qwen, AiTunnel, Anthropic Claude, OpenAI, Codex OAuth models, and local llama.cpp models.
- Local inference through a packaged native bridge with Qwen/Gemma chat profiles, EmbeddingGemma embeddings, prompt-family rendering, strict JSON tool output handling, model downloads, preload/warmup, and cancellation.
- MCP integration over stdio/http with OAuth discovery and token refresh support.
- Voice, ambient, and desktop interaction with audio capture/playback, cloud or local macOS speech recognition, ambient semantic blocks, bounded suggestions, global hotkeys, native media keys, screenshots, screen recording, and macOS integrations.
- ClawHub/OpenClaw skill support with bundle parsing, canonical hashing, compact prompt inventory, desktop-first registry storage, safe on-demand loading, structural/static/LLM approval, validation caching, and sandboxed command execution.
brew tap D00mch/tap
brew install --cask souz-aiOr download the latest build from GitHub Releases.
Run the backend and PostgreSQL locally with Docker Compose:
docker compose up --buildThe backend listens on http://127.0.0.1:8080 and PostgreSQL listens on 127.0.0.1:5432. Override host ports with SOUZ_BACKEND_HOST_PORT and SOUZ_POSTGRES_HOST_PORT when needed. Local defaults use SOUZ_MASTER_KEY=local-dev-master-key and SOUZ_BACKEND_PROXY_TOKEN=local-dev-proxy-token.
Docker Compose uses SOUZ_DOCKER_SUBNET, defaulting to 10.254.250.0/24, for the local service network. Set it to another private subnet before docker compose up if that range overlaps with VPN or corporate routes.
If a VPN route is reachable from the host but not from the backend container, run the backend with host networking:
docker compose -f compose.yaml -f compose.backend-host-network.yaml up --buildProtected /v1 routes expect proxy-injected headers:
curl -H 'X-Souz-Proxy-Auth: local-dev-proxy-token' \
-H 'X-User-Id: 00000000-0000-0000-0000-000000000001' \
http://127.0.0.1:8080/v1/bootstrapThe backend stores provider keys and the default chat model per trusted user. Start Docker Compose first, then set both through the /v1 API with the same X-User-Id.
Store an OpenAI API key for the local user:
curl -X PUT http://127.0.0.1:8080/v1/me/provider-keys/openai \
-H 'Content-Type: application/json' \
-H 'X-Souz-Proxy-Auth: local-dev-proxy-token' \
-H 'X-User-Id: 00000000-0000-0000-0000-000000000001' \
-d '{"apiKey":"sk-..."}'Set that user's default model to an OpenAI API model:
curl -X PATCH http://127.0.0.1:8080/v1/me/settings \
-H 'Content-Type: application/json' \
-H 'X-Souz-Proxy-Auth: local-dev-proxy-token' \
-H 'X-User-Id: 00000000-0000-0000-0000-000000000001' \
-d '{"defaultModel":"gpt-5.2"}'Codex models use one server-managed OAuth session because the refresh token, account ID, and expiry must stay together. Configure CODEX_ACCESS_TOKEN, CODEX_REFRESH_TOKEN, CODEX_ACCOUNT_ID, and CODEX_EXPIRES_AT on the backend, then select a Codex alias such as gpt-5.5 through the settings API. The backend stores rotated OAuth credentials in Postgres with application-layer encryption. Docker Compose persists them in postgres-data; preserve that data and use the same SOUZ_MASTER_KEY when recreating the backend container. The per-user provider-key endpoint does not accept codex.
.
├── agent/ # Shared agent contracts, graph agents, Skill inventory, sessions
├── graph-engine/ # Framework-free typed graph DSL/runtime
├── llms/ # Shared LLM DTOs, provider enums, model profiles, token logging
├── native/ # llama.cpp bridge and local model runtime
├── ambientAgent/ # Ambient transcription semantics and local task analysis
├── sharedLogic/ # Shared JVM runtime logic, providers, tools, and sandboxes
├── sharedUI/ # Shared Compose and desktop UI, view models, host ports, UI adapters, UI resources
├── skill-oauth-api/ # Provider-neutral Skill OAuth contracts
├── skill-oauth-impl/ # Backend OAuth providers, routes, and PostgreSQL persistence
├── desktopApp/ # Runnable desktop host, DI composition root, OS integrations, packaging
├── backend/ # Ktor HTTP backend over the shared agent runtime
├── scripts/ # Build, release, and packaging helper scripts
├── docs/ # Project documentation
└── gradle/ # Version catalog and wrapper configuration
Gradle modules included by the build:
:agent
:graph-engine
:llms
:native
:ambientAgent
:sharedLogic
:sharedUI
:skill-oauth-api
:skill-oauth-impl
:desktopApp
:backend
Module docs:
sharedLogic/README.mdcovers the shared JVM runtime layer, sandbox modes, tools, and Docker sandbox image setup.docs/quality-gates.mdcovers repository checks, evidence, and remediation.
flowchart LR
userNode["User"] --> desktopApp[":desktopApp\nDesktop entry + packaging"]
desktopApp --> sharedUi[":sharedUI\nCompose UI + UI adapters"]
sharedUi --> agentNode[":agent\nGraph agents"]
sharedUi --> ambientNode[":ambientAgent\nAmbient speech analysis"]
backendApi[":backend\nHTTP API"] --> agentNode
agentNode --> graphEngine[":graph-engine\nTyped graph runtime"]
agentNode --> llmsNode[":llms\nLLM contracts"]
runtimeNode[":sharedLogic\nShared runtime logic"] --> agentNode
runtimeNode --> llmsNode
runtimeNode --> nativeRuntime[":native\nLocal llama.cpp runtime"]
runtimeNode --> oauthApi[":skill-oauth-api\nOAuth contracts"]
oauthImpl[":skill-oauth-impl\nOAuth backend implementation"] --> oauthApi
backendApi --> oauthImpl
ambientNode --> runtimeNode
backendApi --> runtimeNode
desktopApp --> runtimeNode
sharedUi --> runtimeNode
sharedUi --> nativeRuntime
runtimeNode --> sandboxNode["RuntimeSandbox\nfilesystem + commands"]
runtimeNode --> toolsNode["Tool catalog"]
llmsNode --> providersNode["Cloud + local providers"]
:desktopApp owns the runnable desktop entry point, app composition root, OS integrations, desktop-only services/tools, and Compose Desktop packaging. It depends on :sharedLogic and :sharedUI.
:sharedUI owns shared UI surfaces and the desktop experience:
- Compose screens, ViewModels, app theme, reusable UI components, and setup/settings flows for desktop.
- Chat UI with model/context selectors, attachments, send/mic controls, streaming state, speech output, and graph/thinking visualization.
- Tool-management UI and permission/selection approval flows.
- Settings UI for models, provider keys, general behavior, security, folders, Telegram, sessions, visualization, and support logs.
- Host-port interfaces plus UI adapters for permission/selection flows and macOS window effects. Non-UI desktop services and OS-bound tools live in
:desktopApp.
UI code should stay rendering-only. Business logic belongs in ViewModels or use cases.
Souz keeps platform-specific logic at the edges:
:llmscontains provider-agnostic contracts and shared model/profile definitions.:graph-enginecontains no LLM/tool/agent knowledge; it only runs typed suspendable graph nodes.:agentimplements agent behavior on top of the graph engine.:ambientAgentcontains shared semantic-block and local task-analysis contracts plus the JVM transcription service.:sharedLogiccontains shared JVM runtime services, portable tools, sandbox/skills infrastructure, provider clients, and platform-specific runtime implementations. SeesharedLogic/README.md.:nativecontains local model support used by desktop and backend-capable runtime wiring.:sharedUIcontains shared Compose and desktop UI, view models, UI adapters, and desktop test coverage.:skill-oauth-apicontains provider-neutral Skill OAuth contracts.:skill-oauth-implcontains the backend OAuth implementation, routes, and PostgreSQL persistence.:desktopAppcontains the runnable desktop entry points, DI composition root, OS integrations, desktop-only tools/services, and packaging resources.:backendexposes the same runtime over HTTP without starting the desktop app.
GraphBasedAgent is the standard tool-calling agent. Its graph is explicit and traceable:
flowchart TD
input["User input"] --> history["Append input to history"]
history --> memory["Recall scoped memory"]
memory --> classify["Classify request / narrow direct tools"]
classify --> skills["Append Skill inventory + core tools"]
skills --> mcp["Inject MCP tools"]
mcp --> enrich["Append additional context"]
enrich --> llm["LLM chat node"]
llm --> decision{"LLM result"}
decision -->|tool call| tool["Execute tool"]
tool --> llm
decision -->|final answer| summary["Summarize / save point"]
decision -->|error| errorNode["Map error to user-facing output"]
summary --> finish["Finish"]
errorNode --> finish
Key behavior:
- Memory recall replaces the previous injected memory block and inserts fresh scoped memory before other turn setup.
- Classification narrows direct tool exposure before the LLM call.
- Skill inventory appends a compact
<skill_inventory>block to the effective system message and exposes on-demand Skill/Knowledge tools. - MCP tools are injected dynamically.
- Tool calls loop back into the LLM until the model returns a final answer.
- Oversized non-exempt tool results are moved to conversation-scoped temporary Knowledge and replaced with references.
- Session history and graph steps can be persisted for replay/inspection.
- The execution delegate supports active-job cancellation and trace callbacks.
- Errors are routed through a dedicated user-facing error node.
SkillsGraphBasedAgent is available under the persisted agent ID skills; GraphBasedAgent under graph remains the default. Its graph keeps tool discovery inside the model-driven tool loop:
flowchart TD
input["User input"] --> boundary["Restrict execution context to fixed core tools"]
boundary --> history["Append input to history"]
history --> memory["Recall scoped memory"]
memory --> inventory["Append skill_inventory block\nTool-backed IDs by category\nFile-backed IDs only"]
inventory --> enrich["Append additional context"]
enrich --> chat["Steerable chat\ninterrupt and replan LLM attempts"]
additionalInput["Additional user input"] --> queue["Execution-scoped FIFO queue"]
queue -.->|cancel active LLM child only| chat
chat -->|accepted core tool calls| tools["Execute complete tool batch\noffload oversized results"]
tools --> chat
queue -.->|tools keep running; drain on re-entry| tools
chat -->|final response and queue atomically sealed| summary["Memory-aware finalization\nsummarize or return"]
chat -->|error and queue atomically sealed| errorNode["Map error to user-facing output"]
summary --> finish["Finish"]
errorNode --> finish
The skills-oriented graph exposes exactly GetSkillByName, GetSkillsByCategory, GetSkillsNamesByCategory, GetKnowledge, SearchKnowledge, SearchMemory, and RunSkillCommand to the LLM throughout a turn. Its execution boundary replaces both advertised functions and executable tool lookup with that fixed core tool set before the graph starts. It does not run direct-tool classification or MCP injection.
Additional input can be submitted only to an open Skills run. It cancels an active LLM request without cancelling the graph, waits for an already-started tool batch, and is appended after all tool results. Finalization begins only after an empty queue atomically seals the run.
Both graph agents append compact Skill inventory to the effective system message while preserving the configured AgentContext.systemPrompt. The inventory lists enabled tool-backed Skill IDs grouped by category plus user-scoped file-backed Skill IDs as opaque escaped identifiers only. File-backed instructions, manifest text, supporting files, bundle hashes, storage paths, and active-skill internals are not embedded in the prompt. Full file-backed bundles are loaded only through exact GetSkillByName lookup or RunSkillCommand execution. Hosts may add SkillApprovalGate; the backend currently invokes its classpath and sandbox-backed providers without a gate.
When conversation-scoped Knowledge storage is available and persistence succeeds, tool-result text larger than 8,192 UTF-8 bytes is retained as temporary Knowledge and replaced in history by a compact reference. Without conversation scope or usable storage, the result remains inline. A result of exactly 8 KiB stays inline. Skill-discovery, GetKnowledge, and SearchKnowledge results always remain inline. GetKnowledge returns all retained content. SearchKnowledge searches retained head and tail segments with UTF-16 offsets; truncated values never match across the omitted gap, and a match without surrounding context omits the redundant excerpt. Knowledge lives until local conversation cleanup, including new-conversation, clear-context, and ViewModel close cleanup. Restoring history after clear-context can therefore restore references whose Knowledge has expired. Backend archive is reversible and does not clear Knowledge.
:graph-engine is a small framework-free runtime for composing typed suspendable Kotlin nodes.
It provides:
Node<IN, OUT>as the unit of work.Graph<IN, OUT>as a node-compatible executable graph.- Static and dynamic transitions.
- Nested graphs.
- FIFO traversal.
- Retry policies.
- Step tracing through
onStep. - Cancellation handling that preserves the last context.
maxStepsprotection against accidental loops.
Run the graph engine README example:
./gradlew :graph-engine:test --tests ru.souz.graph.GraphReadmeExampleTestSouz separates tool behavior from the execution environment through RuntimeSandbox.
RuntimeSandbox
├── mode: LOCAL | DOCKER
├── scope: SandboxScope
├── runtimePaths: home, workspace, state, sessions, vector index, logs, models, native libs, skills
├── fileSystem: SandboxFileSystem
└── commandExecutor: SandboxCommandExecutor
JVM hosts use LocalRuntimeSandbox by default or opt into DockerRuntimeSandbox through SOUZ_SANDBOX_MODE=docker; Docker mode requires the souz-runtime-sandbox:latest image to exist locally. Build it with ./gradlew :sharedLogic:buildRuntimeSandboxImage. Tools plus skill loading, storage, and validation depend on sandbox abstractions instead of directly assuming host access. See sharedLogic/README.md for setup details.
The default JVM state layout is under:
~/.local/state/souz/
├── sessions/
├── vector-index/
├── logs/
├── models/
├── native/
├── skills/
└── skill-validations/
Safety mechanisms include:
- SafeMode permission prompts before sensitive tool execution.
- User approval UI for pending tool requests.
- Deferred review flow for file modifications.
- Confirmation requirement for destructive Telegram operations.
- Ambiguity dialogs for Telegram contact/chat selection.
- Backend tool restriction to backend-safe categories.
- Trusted-proxy identity only for backend
/v1/**routes. - Durable tool-call audit rows in the backend with redacted/truncated previews.
- Opt-in Docker runtime sandbox mode for local app runs and integration tests.
Souz has two tool catalogs:
- Desktop catalog in
:desktopApp, composed with shared runtime tools and surfaced through:sharedUIapproval flows. - Runtime/backend-safe catalog in
:sharedLogic, reusable by:backendwithout instantiating desktop-only services.
| Category | Tools |
|---|---|
| Files | List files, find text in files, create file, delete file, modify file, move file, extract text, find files by name, read PDF pages, open file/path, find folders |
| Browser | Create new browser tab, Safari info, browser hotkeys, focus tab, Chrome info, open default browser |
| Web search | Quick internet search, multi-step internet research, web image search, web page text extraction |
| Config | Sound config, sound config diff, instruction store |
| Notes | Open note, create note, delete note, list notes, search notes |
| Applications | Show installed apps, open app/file/path |
| Data analytics | Create plot from CSV, upload file, download file, read Excel, generate Excel report |
| Calendar | Create event, delete event, list calendars, list events |
| Count unread messages, list messages, read message, reply, send new message, search mail | |
| Text / clipboard | Get clipboard, replace selected text, read selected text |
| Calculator | Calculator |
| Telegram | Read inbox, get chat history, set chat state, send message/attachment, forward message, search Telegram, save to Saved Messages |
| Desktop | Take screenshot, start screen recording |
The backend-safe catalog avoids desktop-only APIs and includes:
| Category | Tools |
|---|---|
| Files | List/find/create/delete/modify/move files, extract text, find files, read PDF pages, find folders |
| Web search | Internet search, internet research, optional web image search, web page text |
| Data analytics | CSV plotting, Excel read, Excel report |
| Calculator | Calculator |
The backend intentionally excludes desktop automation, browser control, Mail, Calendar, Notes, desktop Telegram tools, and other OS-bound tools. It separately supports Telegram bot chat bindings for text ingress into existing backend chats.
Souz treats tool execution as an interactive workflow instead of a hidden side effect.
sequenceDiagram
participant Agent
participant Broker as ToolPermissionBroker
participant UI as Compose approval UI
participant Tool
Agent->>Broker: requestPermission(description, params)
Broker->>UI: emit ToolPermissionRequest
UI->>Broker: approve / reject
Broker->>Agent: Ok / No
Agent->>Tool: invoke only when approved
Confirmation-related flows:
ToolPermissionBrokerserializes SafeMode permission prompts and waits for the user decision.PermissionsUseCaselistens to generic tool permission requests and selection approval sources.ToolModifyReviewUseCasemanages deferred file-modification review/approval inside chat messages.- Telegram tools use selection brokers for ambiguous fuzzy contact/chat matches.
- Destructive Telegram operations require explicit confirmation before continuing.
The desktop host provides a scoped persistent fact store used by agent graphs as untrusted prompt context. Agent graphs accept memory through a host-supplied runtime. The backend uses the no-op runtime unless HINDSIGHT_API_URL and HINDSIGHT_API_TOKEN are both set, in which case it bridges recall, SearchMemory, and completed-turn capture to a self-hosted Hindsight instance with one bank per user. Ordinary turns remain conversation-scoped, explicit remember turns are global, and retained content is limited to redacted user and tool evidence. Natural-language forget or delete requests report exact-ID deletion as unavailable instead of mutating a semantic recall hit or claiming success.
Memory flow:
NodesMemoryrecalls facts relevant to the current user input and injects them as a tagged memory block before tool setup.- Completed turns are handed to the host memory runtime asynchronously after successful finalization; each runtime selects the grounded fields it persists.
- The memory model supports global, project, and session scopes. Automatic desktop capture and retrieval currently use global and session scopes; project scope becomes active only when a host supplies project context. Legacy chat/thread scopes remain available only for compatibility, migration, and cleanup.
- Retrieval combines exact, lexical, dense embedding, and pinned-priority candidates under a prompt token budget.
- Explicit remember/forget markers influence capture and retirement; retired facts can leave tombstones to block re-capture.
- Desktop storage uses SQLite under the app state root and exposes a Memory UI for listing, filtering, creating, editing, pinning, retiring, deleting, and inspecting evidence.
- Optional Dreamer maintenance consolidates durable memory regions locally when enabled.
Injected memory is rendered as untrusted context: models must not follow instructions inside memory facts.
Voice transcription is routed by the selected voiceRecognitionModel:
- Salute Speech, AiTunnel, and OpenAI use cloud STT when their matching provider and API key are available.
Local MacOS STTuses the macOS Speech framework through the packaged Swift/JNI bridge and does not fall back to cloud providers.- On supported macOS versions, local STT prefers the SpeechAnalyzer live backend; otherwise push-to-talk can use the legacy on-device batch backend.
Ambient mode is a local-first proactive-help flow. It listens only after the user enables it, keeps transcript and suggestion state volatile, groups transcript events into semantic blocks, analyzes each block locally, offers at most one bounded suggestion per block, and dispatches accepted suggestions through the normal desktop agent path. Ambient analysis never executes tools or writes memory directly.
:backend is a JVM Ktor server that exposes the shared agent runtime over HTTP.
| Route | Purpose |
|---|---|
GET / |
Public backend route index |
GET /health |
Process and selected-model status |
GET /docs |
Public Swagger UI |
GET /docs/openapi.json |
Public OpenAPI 3.1 document |
GET /v1/bootstrap |
Features, visible models/tools, effective trusted-user settings |
GET /v1/onboarding/state |
Onboarding requirements, model-access hints, and effective settings |
POST /v1/onboarding/complete |
Persist onboarding preferences and mark onboarding complete |
GET /v1/me/settings |
Read public user settings |
PATCH /v1/me/settings |
Persist public user settings |
GET /v1/me/provider-keys |
List configured provider-key state |
PUT /v1/me/provider-keys/{provider} |
Store encrypted provider key |
DELETE /v1/me/provider-keys/{provider} |
Delete provider key |
GET /v1/chats |
List owned chats |
POST /v1/chats |
Create chat |
PATCH /v1/chats/{chatId}/title |
Rename chat |
POST /v1/chats/{chatId}/archive |
Archive chat |
POST /v1/chats/{chatId}/unarchive |
Unarchive chat |
GET /v1/chats/{chatId}/messages |
List visible product messages |
POST /v1/chats/{chatId}/messages |
Create user message and start/complete agent execution |
GET /v1/chats/{chatId}/telegram-bot |
Read Telegram bot binding state for an owned chat |
PUT /v1/chats/{chatId}/telegram-bot |
Validate and upsert a Telegram bot binding for an owned chat |
DELETE /v1/chats/{chatId}/telegram-bot |
Remove the Telegram bot binding from an owned chat |
GET /v1/chats/{chatId}/events |
Replay durable chat events |
WS /v1/chats/{chatId}/ws |
Replay and subscribe to live chat events |
POST /v1/options/{optionId}/answer |
Resume execution after a pending option is answered |
POST /v1/chats/{chatId}/cancel-active |
Cancel active execution |
POST /v1/chats/{chatId}/executions/{executionId}/cancel |
Cancel a specific execution |
- Most
/v1/**routes trust identity only from proxy-managed headers:X-User-IdX-Souz-Proxy-Auth
X-User-Idis treated as opaque and provisioned throughUserRepository.ensureUser(userId).POST /v1/chats,GET /v1/chats/{chatId}/ws, andGET /v1/chats/{chatId}/threads/{threadId}are credential-free Client-Souz exceptions for trusted environments. Chat creation accepts trusted UUIDuserIdfrom the body, and WebSocketmessage.submit.payload.device.userIdmust match the stored chat owner.- Other request bodies are never trusted for user identity.
- Each chat, execution, option, and setting is scoped to the trusted user.
- Backend host adapters replace desktop-only services with no-op implementations.
- The backend composes request-scoped execution from shared agent contracts and graph nodes.
- Backend turns run through one request-scoped steerable
SkillsGraphBasedAgentunderAgentId.SKILLS_GRAPH.
PostgreSQL is the backend's only structured-data store. JDBC, HikariCP, and Flyway provide durable event replay, per-chat message/event sequence numbers, one active execution per chat, optimistic locking for agent_conversation_state, and durable tool-call audit rows.
Telegram bot tokens are encrypted at rest via TELEGRAM_TOKEN_ENCRYPTION_KEY, pending links use one-time /start <secret> commands with only the secret hash stored server-side, and binding setup drops pending Telegram updates before long polling starts.
Runtime sandbox workspaces remain filesystem-backed and are independent from backend database persistence.
# Server
SOUZ_BACKEND_HOST=127.0.0.1
SOUZ_BACKEND_PORT=8080
SOUZ_BACKEND_PROXY_TOKEN=replace-with-shared-proxy-secret
SOUZ_MASTER_KEY=replace-with-settings-secret
# Server-managed provider keys, optional. Docker Compose local setup
# usually uses /v1/me/provider-keys/{provider} instead.
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
QWEN_KEY=...
GIGA_KEY=...
AITUNNEL_KEY=...
CODEX_ACCESS_TOKEN=...
CODEX_REFRESH_TOKEN=...
CODEX_ACCOUNT_ID=...
CODEX_EXPIRES_AT=... # Unix epoch seconds
# Feature flags
SOUZ_FEATURE_WS_EVENTS=true
SOUZ_FEATURE_STREAMING_MESSAGES=true
SOUZ_FEATURE_TOOL_EVENTS=true
SOUZ_FEATURE_OPTIONS=true
ENABLE_BACKEND_TG_FEATURE=true
# Logging
# Human logs go to stdout; one-line JSON logs go to a rolling file.
LOG_LEVEL=INFO
SOUZ_LOG_DIR=${HOME}/.local/state/souz/logs
# Optional proxy-allowlisted User-Agent for outbound web tools.
SOUZ_WEB_USER_AGENT=ProxyApprovedClient/1.0
# Telegram bot
SOUZ_TELEGRAM_POLLING_MAX_CONCURRENCY=4
# Generate once with: openssl rand -base64 32
TELEGRAM_TOKEN_ENCRYPTION_KEY=...
# Provider retries
SOUZ_BACKEND_PROVIDER_MAX_429_RETRIES=2
SOUZ_BACKEND_PROVIDER_BACKOFF_BASE_MS=500
SOUZ_BACKEND_PROVIDER_BACKOFF_MAX_MS=5000
# Postgres
# Optional JDBC URL override for multi-host/TLS deployments.
POSTGRES_DSN=jdbc:postgresql://123.45.67.89:5432,123.45.67.890:5432:5432/souz_preprod?sslmode=require
SOUZ_BACKEND_DB_HOST=127.0.0.1
SOUZ_BACKEND_DB_PORT=5432
SOUZ_BACKEND_DB_NAME=souz
SOUZ_BACKEND_DB_USER=souz
SOUZ_BACKEND_DB_PASSWORD=...
SOUZ_BACKEND_DB_SCHEMA=public
SOUZ_BACKEND_DB_MAX_POOL_SIZE=10
SOUZ_BACKEND_DB_CONNECTION_TIMEOUT_MS=30000The server host must not be blank, and the port must be between 1 and 65535; invalid values fail configuration validation during startup. POSTGRES_DSN must be a PostgreSQL JDBC URL and, when set, replaces SOUZ_BACKEND_DB_HOST, SOUZ_BACKEND_DB_PORT, and SOUZ_BACKEND_DB_NAME; user and password still come from SOUZ_BACKEND_DB_USER and SOUZ_BACKEND_DB_PASSWORD. SOUZ_MASTER_KEY is required for backend startup. Backend Logback writes human-readable console logs and rolling one-line JSON records under SOUZ_LOG_DIR/LOG_DIR, including timestamp, level, logger, thread, message, formatted message, MDC, SLF4J key-value pairs, and throwable details. SOUZ_WEB_USER_AGENT overrides the browser-like default sent by web tools, including the HTTPS CONNECT request when a JVM HTTP proxy is selected. TELEGRAM_TOKEN_ENCRYPTION_KEY is required when the Telegram bot feature is enabled and must be Base64 that decodes to exactly 32 bytes; generate one with openssl rand -base64 32. Without SOUZ_BACKEND_PROXY_TOKEN, public routes remain available but /v1/** requests return backend_misconfigured.
Backend executions snapshot each user's effective enabledTools. The snapshot filters compiled tool-backed Skills once and is retained when an execution resumes from an option. Built-in Client-Souz Skills are merged afterward only for public client executions. The backend model sees only Skill core tools and reaches catalog capabilities through inventory, discovery, and RunSkillCommand.
Run the backend:
./gradlew :backend:runBy default it binds to 127.0.0.1:8080.
Souz supports standalone ClawHub/OpenClaw-style skill bundles across :agent and :sharedLogic.
Skill discovery with an optional host approval gate:
flowchart LR
inventory["Append compact Skill inventory"] --> lookup["GetSkillByName / RunSkillCommand"]
lookup --> skillLoad["Load exact file-backed bundle"]
skillLoad --> gate{"Host configured SkillApprovalGate?"}
gate -->|no| approval["Return instructions or execute command"]
gate -->|yes| skillHash["Canonical hash"]
skillHash --> validationCache{"Cached validation?"}
validationCache -->|approved| approval["Return instructions or execute command"]
validationCache -->|rejected| rejection["Return rejection"]
validationCache -->|missing or stale| structuralValidation{"Structural validation"}
structuralValidation -->|hard reject| rejection
structuralValidation -->|pass| staticValidation{"Static validation"}
staticValidation -->|hard reject| rejection
staticValidation -->|pass| llmValidation["LLM validation"]
llmValidation --> verdict{"Approved?"}
verdict -->|yes| approval
verdict -->|no| rejection
Skill safety and storage:
- Skill inventory is compact and user-scoped: enabled tool-backed Skill IDs by category plus opaque file-backed Skill IDs.
- Tool-backed Skills are direct tools viewed through the Skill APIs; enabled tool-backed Skills take precedence over stored bundles with the same ID.
- File-backed bundle content is loaded only on exact lookup or execution.
GetSkillByNamereturns the file-backedSKILL.mdinstruction body, parsed name and description, and supporting-file paths; raw YAML frontmatter is not returned. A configured approval gate limits this to approved bundles.RunSkillCommandexecutes file-backed Skill scripts inside the resolved runtime sandbox and binds active Skill identity internally.- Bundles are loaded through safe filesystem access.
- Desktop/local skills are persisted under
~/.local/state/souz/skills/{skillId}/, with immutable bundles inbundles/{bundleHash}/and metadata instored-skill.json. - Desktop/local validation records are persisted separately under
~/.local/state/souz/skill-validations/{skillId}/policies/{policy}/. - Validation cache keys include user id, skill id, bundle hash, and policy version.
- Stale validations are invalidated when the active bundle hash changes.
- When a host configures
SkillApprovalGate, rejected validations block instruction lookup and command execution for the exact cached identity. The backend intentionally passes no gate for its classpath- and sandbox-backed providers.
Souz supports:
- GigaChat REST and voice APIs.
- Qwen.
- AiTunnel.
- Anthropic Claude.
- OpenAI.
- Codex through OpenAI device-code OAuth, including GPT-5.3, GPT-5.4, GPT-5.5, and GPT-5.6 Codex model aliases.
- Local llama.cpp models through
:native.
Provider/model selection is key-aware: chat, embeddings, and voice-recognition model lists are filtered by configured provider keys or Codex OAuth state, and invalid saved selections are normalized to available providers.
The backend supports API-key providers, server-managed Codex OAuth models, and local models for chat execution. Per-user provider keys do not represent Codex OAuth sessions.
:native provides local model execution through a JNA bridge into a packaged llama.cpp-based native library.
Features:
- macOS arm64 and x64 packaged bridge binaries.
- Qwen and Gemma chat profiles.
- Linked EmbeddingGemma GGUF asset for embeddings.
- Model storage under
~/.local/state/souz/models/. - Native bridge extraction under
~/.local/state/souz/native/. - Background preload/warmup when selecting a local chat model.
- Settings-driven context windows capped by model limits.
- Prompt rendering for Qwen ChatML and Gemma 4 turn formats.
- Strict JSON tool-call contract and output recovery/parsing.
- Prompt-prefix/KV reuse in the native runtime.
- Local generation and embeddings cancellation support.
Rebuild packaged bridge binaries:
desktopApp/src/main/resources/scripts/build-llama-bridge.shSouz can connect to external tools through Model Context Protocol:
- stdio transport.
- HTTP transport.
- OAuth discovery.
- Token refresh.
- Dynamic MCP tool injection into the agent graph.
Souz has two web modes:
- Internet search for quick factual answers.
- Internet research for multi-step synthesis with LLM-built strategy, broader source coverage, citations, and automatic Markdown export for oversized reports.
Recommended IntelliJ IDEA plugins:
- Kotlin Multiplatform
- Compose Multiplatform
- Compose Multiplatform desktop support, optional
Run the desktop app:
./gradlew :desktopApp:runRun desktop tests:
./gradlew :sharedUI:cleanJvmTest :sharedUI:jvmTestRun agent integration scenarios:
export SOUZ_AGENT_INTEGRATION_TESTS_ON=true
./gradlew :sharedUI:cleanJvmTest :sharedUI:jvmTest --tests "agent.GraphAgentComplexScenarios"Run backend tests:
./gradlew :backend:testRun all checks:
./gradlew checkUseful release scripts:
# Prepare universal macOS app bundle
scripts/kmp-build-macos-universal.sh
# Build notarized arch-specific DMGs and export to dest/homebrew/<version>/
scripts/kmp-build-macos-dev.sh
# Generate Homebrew cask from exported DMGs
scripts/prepare-homebrew-release.shSee JetBrains Compose Multiplatform release docs for signing and notarization details.
- Prefer composition over inheritance.
- Keep UI free of business logic and IO.
- Coordinate UI logic from ViewModels and delegate domain work to use cases.
- Avoid mixing coroutines with low-level JVM concurrency primitives unless there is a clear boundary.
- Use open/closed design for tools, providers, and runtime adapters.
- Keep Compose/UI dependencies out of
:sharedLogic; backend wiring should avoid desktop-only service/tool implementations. - Read the nearest
AGENTS.mdbefore editing a module or nested package.
Copyright © 2026 Artur Dumchev and Shamil Khizriev
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.