Skip to content

config.toml Reference ​

Server settings use a startup-mode-independent platform default: %ProgramData%\LCXL Remote Desktop\config\config.toml on Windows, XDG_CONFIG_HOME/lcxl-remote-desk/config.toml (or ~/.config/lcxl-remote-desk/config.toml) for a non-root Linux user, /etc/lcxl-remote-desk/config.toml for Linux root, and ~/Library/Application Support/com.lcxl.remote-desk/config/config.toml on macOS. -c, --config-file-path <PATH> explicitly selects another profile; services and LaunchAgents inherit it only when it was explicitly supplied.

After saving system, connection, log, TURN, TURN client or AI execution and collection settings, the page rereads the saved configuration and updates its cache. Returning to the page shows the saved values. The save button stays disabled until this refresh finishes; edits made during the refresh are preserved. If the write succeeds but this read fails, the message confirms that settings were saved and asks you to reload the page to check the saved values.

System [system] ​

  • enable_ipv6 — whether to enable IPv6 support.
  • port — server listening port.
  • listen_addr_ipv4 — IPv4 listening address.
  • listen_addr_ipv6 — IPv6 listening address.
  • signaling_url — URL of a standalone signaling server to connect out to (leave empty to use only the embedded signaling server).
  • signaling_token — node access token for the remote signaling server (passed as ?token= on the signaling WebSocket).
  • manager_url — URL of an enterprise manager's signaling endpoint to connect out to.
  • manager_api_token — access token for the manager (passed as ?token= on the manager signaling WebSocket).
  • manager_enabled — whether to keep the manager connection active. Leave unset (or true) to connect; set to false to disable the manager link without clearing manager_url / manager_api_token, so the address is retained for a later re-enable. Toggled from the Desk Connection settings page; this is a host-local switch (the manager cannot turn its own link off).
  • require_secure_signaling — whether this host refuses to dial a public signaling server / manager over a plaintext scheme (ws:// / http://). Leave unset (or true, the secure default) to enforce; set to false only as a deliberate escape hatch for a trusted-network deployment that runs a public endpoint without TLS. Loopback and private / LAN targets (a self-hosted signaling server on 192.168.x.x, 127.0.0.1, etc.) are always reachable over plaintext regardless of this switch, and the cloud-metadata address range is always blocked. Enforcement runs at connect time on the resolved IP, so a domain that resolves to a public address cannot bypass it. Toggled from the Desk Connection settings page. An omitted field fails secure to true.

When the manager fatally rejects this host's registration (its device limit is reached, or the host has no device identity), the desk-server pauses auto-reconnect and the Desk Connection settings page shows a banner explaining why, with a Retry registration button. Free a device slot from a control end, then retry.

  • local_signaling_token — auto-generated, persisted token used by the local desk server (and other hosts) to authenticate with the co-located signaling server. Do not set by hand; it is a credential and is masked in logs.

telemetry_consent is owned by the dedicated Telemetry card rather than the general system-settings form. Changing ports, addresses, IPv6 or auto-start cannot change or clear it. A consent choice is persisted immediately, but the OpenTelemetry exporters/layers are created at process startup; restart the relevant server, daemon and worker processes before describing the new choice as active at runtime.

Local remote-access presentation ​

host_access_indicator_enabled controls whether the Tauri host shows its persistent remote-access card, activity tray badge and first-session notification. It defaults to true. Turning it off only hides these indicators; it does not change approvals, permissions or established sessions. This host-local preference is not remotely configurable by manager. See Remote Access Indicator.

Log [log] ​

  • log_level — logging level (error, warn, info, debug, trace).
  • traceback — whether to enable Rust error backtraces.
  • log_retention_days — log retention in days (default 7). 0 disables cleanup.
  • log_cleanup_threshold_percent — disk-usage threshold that triggers cleanup (default 90). Once the disk is fuller than this, rolled files inside the retention window are deleted oldest-first until usage drops back under it; today's and yesterday's files are always kept, because an appender may still hold them open. 0 disables this stage, leaving retention as the only criterion.
  • log_cleanup_interval_hours — interval in hours for the cleanup task (default 12). 0 disables cleanup.
  • tokio_console_enabled — enable the tokio-console subscriber (requires the tokio_unstable build flag, default false).

Cleanup covers every component's rolled files in the log directory (desk-server.log.<date>, desk-daemon.log.<date>, desk-worker.log.<date>, desk-mcp.log.<date>, desk-tauri.log.<date>); files that belong to other programs sharing the directory are left alone. Every file carries a date suffix, including the one being written right now — retention is measured from it, so the live file is never removed. Roll dates are UTC.

Logging never blocks the service: stdout and the log file are both written through a dedicated writer thread behind a bounded, lossy queue. If a sink stops draining — a full or hung disk, a console that stopped servicing writes for an inactive desktop — records are dropped rather than backpressured onto the caller, so capture, input injection and signaling keep running.

The sweep runs in the process that owns the log directory: the daemon, or the server in a portable / signaling-only run. Session workers and the MCP stdio helper never sweep — they are short-lived and hold a startup snapshot of the settings, so a change to the values above would not reach them.

User [user] ​

  • login_user_name — initial login username.
  • login_password — initial login password.

TURN Server [turn] ​

  • realm — TURN server realm for authentication.
  • interfaces — the addresses this host relays on (see Interface addresses).
  • static_auth_secret — static authentication secret.
  • enable_turn — run the TURN service on this host (default true). One service provides both TURN relay and STUN, so turning it off stops both; connections then rely on whatever relay the signaling server offers.
  • relay_min_port / relay_max_port — relay port allocation range.
  • [turn.static_credentials] — optional static username / password credential table.

The TURN service follows these settings while the server runs: saving the TURN settings page (or regenerating the secret) restarts the service immediately, with no server restart. A restart drops the connections currently being relayed — they reconnect through whatever candidates ICE can still find. Sessions that are not going through this relay are unaffected.

With enable_turn = true but no interfaces configured, nothing is started (the service has no address to serve on); the runtime status endpoint reports this as not-configured, which is deliberately distinct from having switched the service off.

Interface addresses ​

Each entry has a transport, a listen address (what this host binds) and an external address (what peers are told to dial):

toml
[[turn.interfaces]]
transport = "udp"
listen = "0.0.0.0:3478"
external = "203.0.113.7:3478"

[[turn.interfaces]]          # IPv6 literals need their brackets
transport = "udp"
listen = "[::]:3478"
external = "[2001:db8::1]:3478"

Both addresses are IP:port pairs. Host names are not resolved — write the address itself. An external value additionally has to be something a peer can dial, so a wildcard address (0.0.0.0, ::) or a zero port is rejected.

Only UDP is relayed. A tcp entry is neither listened on nor advertised, rather than advertised and left unanswered.

An entry that fails any of these is reported and skipped; the remaining entries are served normally. Each rejection is logged at startup and listed in the runtime status endpoint's rejected_interfaces, naming the entry, the field and what a working value looks like. If every entry is rejected there is nothing to serve on, so the status is not-configured — with the rejections attached, which is what distinguishes it from having configured no interfaces at all.

The TURN settings page shows the same information as a runtime status card: whether the service is running, the interfaces it is actually serving, why it is not running when it is not, and any entry that was refused. The card is what to read after saving — the form shows the configuration, the card shows what became of it.

A save returns before the ports are bound, so immediately afterwards the card reads starting rather than running — that is the normal path, not a failure, and only a state with a cause to report is called one. The card re-reads itself while the host is still settling, so it becomes running, or names what went wrong, without being refreshed by hand.

The page also has a collapsed Advanced statistics lookup. Given a known client IP:port and interface, it shows relay and control bytes/packets for that address and distinguishes a stopped runtime from an address with no record. This TURN implementation cannot enumerate all sessions or force-close one, so /api/turn/session/statistics is the only session endpoint the API exposes — a per-address lookup, with no list or teardown counterpart.

Desktop [desk] ​

  • video_fps — video frame rate (default 60). Lowering reduces CPU and bandwidth usage.
  • video_quality — video encoding quality (0–63, lower is better, default 22).
  • video_encoder / audio_encoder — optional; auto-selected when omitted. Video may be X264 / VP8 / VP9 / H264 / AV1; audio is Opus.
  • video_device_name — GDI device name of the monitor to capture (\\.\DISPLAYn); empty means "ask the browser to pick on first connection".
  • show_mouse — whether to capture and display the mouse cursor.
  • enable_dirty_rect — whether to enable dirty-rectangle incremental encoding.
  • [desk.private_screen] — privacy screen settings (enabled, etc.).

Virtual Display [virtual_display] ​

  • enabled — whether to enable the virtual display (requires an installed IddCx driver; effective only in specific modes).
  • exclusive / prompt_ms / adaptive_* — exclusive-mode and adaptive-resolution parameters.

Security [security] ​

Per-capability access control for inbound remote sessions. Each capability is tri-state: unset means "ask the local user each time" (the file default), true means "always allow", false means "always deny".

The onboarding wizard writes an explicit posture at install time (opening capabilities for the owner), so a wizard-installed host does not start from the all-ask file default. For a non-owner session redeemed via an access code, these global settings are additionally met with that code's capability ceiling and live approval.

  • allow_remote_control — mouse / keyboard input.
  • allow_clipboard_sync — clipboard synchronization.
  • allow_system_audio_capture — capture and transmit the host's system audio.
  • allow_private_screen — private (privacy) screen mode.
  • allow_whiteboard — whiteboard overlay.
  • allow_terminal — remote terminal access.
  • allow_file_browse — directory listing and file metadata.
  • allow_file_delete — file deletion; a delete also requires allow_file_browse.
  • allow_file_transfer — file upload / download.
  • approval_timeout — how long an approval prompt waits, in seconds. Default 30 — after which the host server authoritatively cancels (denies) the request rather than leaving it pending forever, enforced server-side even if the approval UI is closed or unreachable. Set to 0 to never time out (the prompt waits indefinitely). "Never" is stored as the value 0, so it survives a restart.

When a change takes effect ​

A capability change applies to sessions that are already running — there is no need to disconnect the controller or restart the host. The new setting is in force from that connection's next request for the capability.

What it does not do is retract an ability the controller already holds. Setting allow_remote_control to false while someone is controlling the desktop stops the next control request, but does not end the control they were granted; the same goes for a transfer already in progress. To cut a session off immediately, disconnect it (or use remote access lock, which cancels everything outright).

A "remember my choice" answer given while the setting was being changed is discarded rather than applied — the change you just made stands, and the request the user answered is still honored on its own.

OSS global configuration ​

OSS saves AI gateways, search and global policies in the same config.toml as host settings, selected by the active profile or --config-file-path. Settings pages persist changes immediately. To edit the file manually, stop the service, edit, and restart. These sections and [oss_config_metadata] do not accept LRD_* overrides; other host settings keep their existing environment behavior.

SectionFields and meaning
[device_codes]length: generated length for new device codes; default 8, range 6–64. Applies to first assignments and explicit regenerations; existing codes stay unchanged
[access_code_protection]Access-code request limits and failure backoff; also editable on the Device Codes settings page
[ai_gateway]wire_protocol, base_url, model, api_key, supports_image_input; profile_schema_version, reasoning_contract, anthropic_prefix_binding, request_options_json, output_limit_field, runtime_max_output_tokens, max_context_bytes; response_format, execution_mode, max_steps_per_turn, max_same_tool_calls_per_turn, exec_approval_timeout_secs (seconds)
[approval_gateway]enabled; its independent model settings are in [approval_gateway.gateway], with the same fields as the main gateway. OSS has no price, currency or accounting settings
[web_search]schema_version, provider (duck_duck_go, brave, tavily), optional api_key
[context_management]schema_version, strategy (window, checkpoint_summary)
[subagent_policy.limits]maxUnfinishedPerRoot: maximum unfinished subagents for each root conversation
[goal_budget_policy]schemaVersion, deviceUnavailableMaxMs (milliseconds, cannot be disabled); [goal_budget_policy.limits] contains activeTimeMs, deadlineMs (milliseconds), modelTokens, modelCalls, toolCalls, slices, stalledSlices
[schedule_budget_policy.maximum]max_runs_per_utc_day, max_calls_per_run, max_model_tokens_per_run, max_runtime_seconds (seconds)
[usage_retention]turn_days, agent_session_days (days)
[model_metrics]enabled, detail_days, five_minute_days, hourly_days, mutable_days (days), detail_row_budget, event_row_budget, compact_row_budget, rollup_row_budget, series_per_bucket, storage_budget_bytes (bytes, decimal string)

AI gateway and approval gateway tests use their respective runtime_max_output_tokens, sharing the output limit with runtime calls. There is no separate probe output token setting.

Missing sections or fields use the existing business defaults. Unknown fields within these sections and invalid values reject loading. Revision fields (revision, connection_revision, profile_revision, configuration_revision) use decimal strings, for example revision = "1". The service maintains revisions and the instance identity, fingerprints and revision records in [oss_config_metadata]; leave these fields managed by the service. Startup reconciles actual business changes after a stopped-service edit; whitespace and key ordering do not advance revisions. Gateway probes remain in SQLite; revalidate an edited gateway when prompted by the settings page.

request_options_json is a JSON object string and preserves nested null. Each optional goal limit accepts a positive integer or the string "disabled"; omission uses its default. The device-unavailable wait bound always requires a valid positive integer.

toml
[ai_gateway]
wire_protocol = "anthropic_messages"
base_url = "https://model.example/v1"
model = "example-model"
api_key = "replace-with-your-key"
max_context_bytes = 131072
request_options_json = '{"thinking":{"type":"adaptive","display":null}}'

[approval_gateway]
enabled = false

[goal_budget_policy.limits]
modelTokens = "disabled"

[usage_retention]
turn_days = 30
agent_session_days = 30

The file contains API keys. Public settings responses still return only whether a key is set. Complete-document saves and the existing worker initialization retain all sections. Context policy stays pinned when each model seam first uses it; goals apply current budgets at their existing checkpoints without resetting counters; retention changes apply on the next cleanup cycle.

desk_signal.db retains sessions, tasks, permissions, probes and usage; model-metrics.sqlite retains metric records and runtime watermarks. During development, stop the service and manually rebuild incompatible runtime databases. The new format does not read, convert or migrate old configuration tables. Settings already saved in config.toml survive a database rebuild.

toml
[log]
log_level = "debug"
traceback = true

[desk]
video_fps = 30               # Reduce FPS during development to save resources

Purpose output limits ​

The Context management and Terminal completion pages save the following global sections. Settings submission manages revision. Values must be integers from 1 to 4294967295; effective output is the smaller of the purpose value and the model runtime limit.

toml
[context_management]
schema_version = 2
revision = "0"
strategy = "checkpoint_summary"
summary_max_output_tokens = 16384

[terminal_completion]
revision = "0"
max_output_tokens = 512

Thinking request profile ​

The current profile schema is 2. Set reasoning_contract explicitly to conservative, openai_chat, deepseek_chat or anthropic_messages, matching wire_protocol. anthropic_prefix_binding defaults to false; enable it only for an Anthropic endpoint that binds signed thinking to the request prefix. Thinking effort and optional native clearing remain inside request_options_json; see AI diagnostics. Both the main and approval gateways use these fields.

Assistant data policy files use the same field checks as the management API. Invalid values report their fields; startup and successful policy updates log only matching warnings. See AI Assistant memory and history policy for warning semantics and shared read budgets.

AI Assistant memory and history policy ​

AssistantDataPolicy.limits controls memory content and retrieval work. After signing in with the open-source Signal management account, GET /api/admin/system/ai-assistant-data returns data.policy and data.issues. Submit the edited complete policy with expected_revision and policy using POST, keeping the revision that was read. A successful save returns the committed policy and its matching issues. Reload after a revision conflict. Signal atomically saves the active config.toml.

Valid configurations return an empty issues list. Each issue contains level (error or warning), a stable code, related configuration fields, a fallback message, and effective_limit when applicable. Hard errors use HTTP 200 with success=false and INVALID_PARAMS, without applying the save. Warnings retain success=true and allow saving. Revision conflicts keep REVISION_CONFLICT and current_revision.

Warnings cover only limits proven by the configuration: relevance page size exceeding candidates, history candidates exceeding conversations multiplied by fragments per conversation, model status summaries exceeding retained items, owner history scans exceeding the document budget, and current-memory selection exceeding its storage or scan ceiling. Actual body, index, query-state and metadata sizes depend on content and do not produce estimated capacity warnings. Invalid OSS file settings identify their fields. Startup and successful policy updates log only matching warnings.

New reads stop when the episode has zero remaining bytes. Positive allowances use the smaller of the per-response cap and the remaining total. Actual JSON and tool-message framing determine whether required metadata and whole sources fit. Call counts, actual settlement and original-call replay retain their existing budgets. History, memory and retained versions simultaneously consume their count, body, index and metadata quotas. Reaching the retained-version count limit still blocks updates until version cleanup frees capacity.

Chat, subassistant and manually scheduled task text is submitted intact. The server validates text size, memory counts and data-result quotas. All three clients display server-provided memory, history, citations and processing status while checking types, identities, versions, source bindings, duplicates and cursor consistency. Personalization and keyword prechecks read the current server policy, and ordinary lists keep their chosen page sizes. A refused submission reports the failure and preserves the editable draft; chat input clears after acceptance is confirmed.

Ordinary chat input, citations and status display use the same platform policy. Paths below are relative to policy.limits:

Configuration pathDefaultPurpose
conversation.question_bytes16 KiBUTF-8 admission size for ordinary chat text
conversation.page_bytes256 KiBShared page target for owner history search, retained conversations, source windows and chat messages
conversation.citations / citation_bytes / citation_page_answers64 / 8 KiB / 100Citations per answer, encoded reference capacity and citation loading batch
retrieval.snippet_bytes512 bytesSearch snippets, truncated only at UTF-8 character boundaries
retrieval.episode.query_devices / query_cursors10,000 / 32Device scope and continuation tokens for a frozen query
retrieval.episode.query_state_bytes / query_seen_sources / query_delivered_calls1 MiB / 500 / 32Query encoding, source deduplication and delivered-call ledger
processing.status_coverage50History coverage records
processing.native_status_rows / native_status_items / native_status_model_items / native_status_source_refs25 / 24 / 3 / 4Native rows per category, snapshot items, model-summary items and summary sources

Maintenance uses its original source-manifest policy; query capacities are frozen with the original retrieval budget and remain unchanged on retries and pagination. Lower citation limits apply to new answers while existing references remain readable; the loading batch controls database work per step. Internal source ledgers, read projections, export groups and fact commits use the original request budgets, account record quota and actual metadata quota instead of separate arbitrary collection caps. Actual authorization deadlines, source bindings, native receipts and transactional version checks remain in force. Clients display service-provided proposals, previews and attachment policies, and submit complete scheduled-task text, disposition notes and directory-purpose text; the service reports quota failures through the existing business-error response.

The top-level platform field policy.max_memory_records limits stored memory records to 1,000 per account by default, shared across personal and device scopes. Each independent memory record counts once; revisions and idempotent replays do not add a record. Retained pending, superseded and forgotten records also occupy storage slots. New records fail and roll back at the limit; updates continue within the existing body and metadata quotas. Lowering the limit preserves existing records, and raising it permits further additions. Save this field through the complete policy endpoint above; it sits alongside policy.limits.

conversation.page_bytes is the shared page target for owner history search, retained conversation lists, retained source windows and ordinary or source-focused chat pages. Pages preserve complete fragments, native tool groups, reasoning and citations. One indivisible entry or group may exceed the target and is returned intact; neighboring content is paginated. Existing item-count and access rules remain. This target does not reject accepted message bodies. Owner search uses retrieval.query_candidates, encodes cursors within retrieval.episode.query_state_bytes, and loads matching terms from the actual query term set. Source discovery uses the existing document, scan-page and time budgets. Owner search cursors remain bound to their original policy revision; restart the search after a policy change. Retained memory sources remain readable and forgettable when memory.source_refs is lowered; that limit applies to new admission.

Owner retained-history windows return up to 20 complete fragments per page, with cursors for earlier or newer content and total window size determined by the actual content. Model tools continue to use retrieval.episode.response_bytes for each reply.

Each keyword group defaults to 256 Unicode scalar values. A Chinese character or a single-code-point emoji counts as one; combining scalars count separately. Group count and characters per group are separate settings. All five retrieval scan time limits default to three seconds. Content, context, evidence, and personalization capacities use UTF-8 bytes. Omitted configuration fields use their defaults.

retrieval.read_messages controls conversation messages or memory versions per read, defaulting to 20. retrieval.episode.response_bytes sets one complete reply budget for memory search, history search and content reads, defaulting to 32 KiB including JSON and tool-message framing. Content reads also use the episode’s remaining read_bytes. New episodes freeze these settings; retries and pagination keep the original reservations and spending.

Memory revision reads also share retrieval.query_candidates for bounded scanning and retrieval.memory_keyword_scan_ms for scan time. Budget limits can produce further pages, reported gaps or an insufficient-budget result.

Platform context, individual tool-response capacity, source authorization and actual storage quotas still apply. Lower content limits constrain later writes; existing content remains readable under its original identity and digest.

New ordinary-input and task retrieval episodes freeze their allowances. Approval, recovery and pagination retain spending. Model tool schemas, Web and mobile input controls use the policy limits.

Query snapshots/cursors (retrieval.episode.query_ttl_ms) and unused read results (retrieval.episode.unused_projection_ttl_ms) each default to 1,800,000 ms (30 minutes). Positive durations freeze with the original retrieval episode; new ordinary inputs or a task occurrence’s first claim read the current policy. Recovery, approval and Goal corrections keep the original values. Query and tool-result deadlines start at the original reservation, while an automatically injected memory block starts at preparation; earlier task, source and execution-lease deadlines still apply. Pagination and retries retain the stored absolute deadline. Results saved in the current conversation follow that conversation’s lifecycle; temporary current-memory blocks retain per-request authorization checks. Policy revision checks continue to apply.

toml
[assistant_data_policy.limits.memory]
user_instructions_bytes = 16384
source_refs = 12

[assistant_data_policy.limits.retrieval]
query_groups = 6
query_chars = 512
search_window_days = 120
history_keyword_scan_ms = 5000
owner_scan_documents = 256

[assistant_data_policy.limits.retrieval.episode]
relevance_calls = 6
time_pages = 12
read_calls = 10
read_bytes = 262144
query_ttl_ms = 1800000
unused_projection_ttl_ms = 1800000

Memory writes ​

The conversation model submits complete content and optional context. One transaction saves the current memory, version, source snapshots, index and commit receipt. Current context, memory-count, body, index and metadata quotas apply. Capacity failures roll back the write while preserving existing memories. Normal conversation output and accounting limits apply.

Background processing policy ​

GC intervals and working batches are configured through policy.limits.processing. Manager reads the shared database policy; OSS reads its active config.toml. Background loops read settings on their next wake-up; retained retrieval episodes keep their frozen policy and authorization. The existing retention loop drives GC every five minutes by default, using the same interval for per-account claims. Other usage, session and call retention remains hourly. Scan pages bound database work per pass; the capture batch limits each history submission. Existing leases, call identities, native receipts, policy revisions and accounting rules still apply.

Path relative to policy.limitsDefaultMeaning
processing.output_gc_interval_ms300000GC wake-up and per-account rescan interval, ms
processing.output_gc_actor_batch_size32Accounts per GC pass; OSS scans only its local account
processing.output_gc_batch_size64Output-proof and commit-receipt batch per account
processing.native_retention_page_size64Native-receipt retention page
processing.history_capture_batch_size64Captures per history submission
processing.history_scan_page_size128Database page for history time/window scans
processing.current_memory_page_size64Database page for current-memory selection
toml
[assistant_data_policy.limits.processing]
output_gc_interval_ms = 300000
output_gc_actor_batch_size = 32
output_gc_batch_size = 64
native_retention_page_size = 64
history_capture_batch_size = 64
history_scan_page_size = 128
current_memory_page_size = 64