📘 New here? Start with the plain-language explainer How Pulse runs its background services — this page is the reference it links down to.
The ProcessManager subsystem manages the lifecycle of child processes (Python services, connectors) within the Tauri application. It tracks process instances in a SQLite database and broadcasts logs.
Sources:
clarity:backend/src-tauri/src/process_manager/supervisor.rsclarity:backend/src-tauri/src/process_manager/http_api.rssupervisor.rsSource: clarity:backend/src-tauri/src/process_manager/supervisor.rs
Initialized at app startup with a dedicated SQLite database (process_registry.db) and a logs/ directory:
clarity:backend/src-tauri/src/main.rs:2017-2026
let process_manager = ProcessManager::new(process_db_path, data_path.join("logs"))
ProcessConfig fields:
process_name: unique identifierbinary_path: executable pathprocess_type: Rust or (presumably) Pythonworking_dir: optional working directoryargs: command-line argumentsenv_vars: environment variable overrideshealth_check_url: optional HTTP endpoint for liveness checkshealth_check_interval: seconds between health checksrestart_policy: No, or auto-restart variantsmax_restarts, restart_window: restart throttlingauto_start: whether to restore on app relaunchclarity:backend/src-tauri/src/main.rs:2087-2100
| Method | Purpose |
|---|---|
register_process_if_not_exists(config) |
Register a process definition |
create_process_instance(proc_id) |
Create a running instance record |
restore_all_processes() |
Re-spawn all auto_start=true processes on app restart |
cleanup_orphaned_processes() |
Kill processes from a previous app run that were not properly terminated |
kill_all_previous_processes() |
Kill all PIDs tracked from the previous session (blocking) |
log_to_db_async(instance_id, level, message) |
Persist a log entry for a process instance |
broadcast_log(entry) |
Send log entry to all WebSocket subscribers |
register_standard_internal_tasks() |
Register built-in task factories |
start_watchdog() |
Spawn the auto-start health-check watchdog (see below) |
clarity:backend/src-tauri/src/main.rs:2028-2066
start_watchdog() spawns a background task that periodically restarts any auto-start process that has stopped. As of c41a03b it waits 30 s on first boot (was 2 min), then checks every 5 minutes (300 s, was 1800 s): for each auto-start process id, if it is not in running_processes, it calls start_process() to restart it. The task holds a Weak<Self> and exits cleanly once the ProcessManager is dropped. It is started once from main.rs after the Python services are registered.
clarity:backend/src-tauri/src/process_manager/supervisor.rs
On start_process(), if a previously-tracked PID is still alive, the manager kills it and spawns fresh rather than adopting it. Adopted processes ran with stale config/env and could not be cleanly managed. The dead-PID branch and the kill-leftover branch both mark the old DB instance stopped (guarding against a missing instance_id instead of unwrap-panicking).
clarity:backend/src-tauri/src/process_manager/supervisor.rs:497-527
restore_all_processes() is now idempotent: it skips (and records as started) any process already present in running_processes, so a watchdog-triggered restore won't double-spawn.
clarity:backend/src-tauri/src/process_manager/supervisor.rs:829-836
c41a03b (supervisor/health/db)Command gets .hide_console() (win_console::HideConsole) — the release build is GUI-subsystem, so console-subsystem children otherwise flash a window.tasklist.exe subprocess spawns with the sysinfo crate (System::new() + single-PID refresh_process), and now verify the exe path matches instead of a "python" substring; health.rs's check_process_alive likewise dropped System::new_all()+full-table refresh for a single-PID refresh.ProcessType::Python → continue) because they are owned by setup_python_services — fixes the "random service failure after reboot" double-owner race.get_total_restart_count(process_id, restart_window) filters on started_at >= cutoff in SQL (window_secs == 0 = all-time) so flapping counts stop accumulating across sessions.log_process_output_batch (one transaction/fsync, cached prepared statement) instead of per-line inserts; same warn→info/trace→debug level remap.logs_ui.html: the user-controlled process name is no longer written via innerHTML.clarity:backend/src-tauri/src/process_manager/{supervisor.rs,health.rs,db.rs,logs_ui.html}
Platform note: Windows only.
ProcessManager now creates a Windows Job Object at construction (CreateJobObjectW + SetInformationJobObject with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE). Every spawned child is assigned to it via OpenProcess(PROCESS_ALL_ACCESS) + AssignProcessToJobObject. When Clarity exits — including a Task Manager kill or a crash with no shutdown handler — the OS closes the job handle and terminates all assigned children, preventing orphaned Python services. If job creation/configuration fails, a warning is logged and spawning continues without the guarantee (handle stored as 0). The job_object: HANDLE field is #[cfg(windows)]-only, and ProcessManager carries unsafe impl Send/Sync on Windows because the raw handle is only touched from its own methods.
clarity:backend/src-tauri/src/process_manager/supervisor.rs:36-41, 181-262, 670-695
The required Win32 bindings come from a new [target.'cfg(windows)'] dependency windows-sys = "0.52" (features Win32_Foundation, Win32_System_JobObjects, Win32_System_Threading). clarity:backend/src-tauri/Cargo.toml:93-99
tasklist /fi "PID eq <pid>" /fo csv /nh with the CREATE_NO_WINDOW flag (replacing wmic, which is deprecated/removed on newer Windows). A PID is treated as Python only if the output is not "No tasks" and contains "python". clarity:backend/src-tauri/src/process_manager/supervisor.rs:58-71taskkill /F /T /PID (and the graceful path taskkill /T /PID) — the /T flag terminates the entire process tree, so child processes spawned by the Python venv launcher are also killed. clarity:backend/src-tauri/src/process_manager/supervisor.rs:407-411, clarity:backend/src-tauri/src/process_manager/supervisor.rs:744-770When stopping Python services, the ProcessManager can kill by port number (not just PID) to handle cases where the PID is unknown or stale.
Implementation in clarity:backend/src-tauri/src/process_manager/supervisor.rs:73-120:
lsof -ti :<port> → parse PIDs → verify PID is a Python process → kill -9 <pid>netstat -ano → find LISTENING line for port → parse PID → verify Python → taskkill /F /PID <pid>Safety guard: kill_port only kills processes where is_pid_python(pid) returns true; skips browsers, editors, or any other non-Python listener on the same port.
The main thread spawns a log consumer task that routes log messages to per-service files based on the target field:
clarity_lib::mqtt_services → mqtt.logclarity_lib::api::python_proxy → python_proxy.logclarity_lib::api::tag_resolver → tag_resolver.logclarity_lib::sqlite_api::api::tag_mappings → tag_mappings.logclarity_lib::python_services → python_services_manager.logclarity_lib::monitor::auto_rules / sqlite_api::api::tagmeta_hooks → auto_rules.logclarity::licensing → licensing.log (new — see Licensing)clarity_main.stdout.logLog rotation: each file truncated to 0 bytes when it reaches 100 MB.
clarity:backend/src-tauri/src/main.rs:2113-2219
Log tailer startup retry: spawn_log_tailer retries opening a process's log file once per second for up to 30 seconds before giving up — a process may take a few seconds to create its file on startup, so this avoids dropping the tailer on a transient miss. clarity:backend/src-tauri/src/process_manager/supervisor.rs:1274-1300
mdns_sd noise suppression: the ProcessManagerLogger adds mdns_sd to its stdout-suppression list. mdns_sd logs at ERROR for IPv6 link-local interfaces that have no global address for AAAA records; this is harmless (mDNS continues over IPv4) and is no longer printed. clarity:backend/src-tauri/src/process_manager/logger.rs:59-65
http_api.rsSource: clarity:backend/src-tauri/src/process_manager/http_api.rs
Registers Warp routes for controlling processes over HTTP:
| Method | Path | Description |
|---|---|---|
| Method | Path | Description |
| --- | --- | --- |
| GET | /process-logs |
Serves HTML UI for log viewing |
| GET | /process-logs/ws |
WebSocket: on connect sends {type:"process_list"} + last 1000 log entries; then streams {type:"log", entry: LogEntry} |
clarity:backend/src-tauri/src/process_manager/http_api.rs
No start/stop/status HTTP endpoints exist. Process start/stop is managed programmatically via the ProcessManager API, not via HTTP.
Last updated: 2026-07-11 from clarity@c41a03b
Range9847dba..c41a03bnote:types.rsandlogger.rsgained#[cfg(test)]modules only (no behavior change).