nikolacucuk/execlaw

Rust

0

79 commits

updated Sep 18, 2026

See the code

README

execlaw

CI codecov License: Apache 2.0

Self-hosted Rust agent framework with persistent memory, hook-based plugins, tools, and skills. Bare metal best metal. All inference runs on operator hardware.

execlaw — Skills page execlaw — Deep research session

Documentation

DocWhat it covers
docs/architecture.mdSystem topology, design principles, FSM, data model, recovery, observability — the what.
docs/agent-model.mdTurnExecutor, memory layers, reflection loop, planner/executor split — the how of one turn.
docs/memory-roadmap.mdGoverned memory assets, task/agent loadouts, hybrid retrieval, local Wiki, and CodeGraph implementation checklist.
docs/plugins.mdPlugin manifest schema, runtime tiers, sidecar model, Rhai primitives, and a step-by-step guide for writing a custom plugin.
docs/operator-decision-rubric.mdStructured rubric for placing features in plugins vs MCP vs host core, plus tool-chaining and learning-loop guidance.
docs/hermes-porting-todo.mdHistorical completion checklist for Hermes-originated capabilities ported into execlaw.
docs/setup-walkthroughs.mdOperator-facing pairing flows for Signal QR, WhatsApp wuzapi, Slack OAuth, Google OAuth + API-key.
docs/desktop-installations.mdCross-OS reference for the three desktop bundles — .app/.dmg, NSIS .exe, .deb. Tray architecture, service-manager mapping, install + uninstall flows, build scripts.
docs/ollama.mdPre-installed Ollama support across macOS / Linux / Windows. How discovery works, when to pick Ollama over Docker, the wizard's serving dropdown.
docs/copilot-graphify-obsidian-workspace-setup.mdStep-by-step guide to reproduce Graphify + Obsidian memory workflow in other repositories using GitHub Copilot.
docs/setup-mac.mdApple Silicon first-run notes — native Ollama subprocess, model sizing, brand indicator.
docs/truenas-docker.mdTrueNAS SCALE Docker deployment with persistent ZFS storage and external Ollama.
desktop-macos/README.mdmacOS .app bundle internals — Tauri 2, SMAppService, build script.
desktop-windows/README.mdWindows NSIS .exe bundle internals — Tauri 2, SCM service, build script.
desktop-linux/README.mdLinux .deb bundle internals — Tauri 2, systemd --user unit, build script.
docs/security.mdDisclosure path, threat model, cryptography, trust assumptions, known limitations, hardening checklist.
docs/security-hardening-2026-06.md2026-06 hardening pass: HTTP security headers, login rate limiting, expanded homoglyph coverage, webhook auth enforcement.
docs/sidecar-supervisor-design.mdSupervised-container layer plugins compose against.
docs/runner-design.mdPer-conversation runner container model.
docs/voice-followups.mdVoice modality design notes.
CONTRIBUTING.mdWorkflow, code conventions, AGPL→Apache-2.0 licensing notes.
AGENTS.mdOnboarding for AI coding agents working on this repo.

Developer tooling note (Graphify CLI)

Install the official graphifyy package (double y) with uv:

& "$HOME\.local\bin\uv.exe" tool install "graphifyy[sql]" --python 3.12
& "$HOME\.local\bin\uv.exe" tool update-shell
& "$HOME\.local\bin\graphify.exe" update .

Open a new terminal after PATH changes. graphify update . uses local AST extraction, not an LLM. The root .graphifyignore excludes alternate Cargo build trees and nested graph output. Check the result with graphify query "MessageStream" or graphify path MessageStream useChatAppearance.

Windows terminal and validation

The workspace VS Code terminal profile binds Ctrl+U to BackwardDeleteLine. Without that binding, line-clear input can appear as a literal ^U and break otherwise valid commands. Open a new terminal to apply the profile. Existing terminals can run Set-PSReadLineKeyHandler -Chord Ctrl+u -Function BackwardDeleteLine.

Execution policy is unchanged. Use npm.cmd when Restricted policy blocks npm.ps1. Tasks use a separate automation profile to avoid colliding with interactive startup arguments. Tasks: Run Task -> web: verify runs tests, type-check, build, size budgets, browser checks, and dependency audit.

Install Chromium once before browser checks:

node web/node_modules/@playwright/test/cli.js install chromium
npm.cmd --prefix web run build
npm.cmd --prefix web run test:appearance

The browser check captures dark/light screenshots at 1440, 390, and 320 pixels in a printed temporary directory. It checks source navigation, Classic restoration, streaming, and production bundle boot with mocked API responses. Its loopback servers and browser close after the test. It never starts the operator backend, accesses the vault, or sends real transport messages.

What ships today

  • Control plane (Rust binary): event log + scheduler + plugin host + container manager + outbox relay + axum server + SQLCipher vault.
  • Per-conversation runner containers: stateless against the log, stateless OpenAI-compatible client to local inference (vLLM / OpenArc / Whisper / Kokoro).
  • Trust ladder + Rule of Two: Controller / Delegated / KnownTrusted / KnownLimited / UnknownPending / Blocked with cold-contact escalation, signed approval-token JWTs, sideband HITL.
  • HMAC-chained event log: every committed row is tamper-evident; replay rebuilds state deterministically.
  • Deterministic social archive: transport messages are retained in SQLite and mirrored as Obsidian-compatible Markdown under .obsidian/archive/<transport>/<group|direct>/, without LLM calls; SQLite FTS5 provides exact search and the files are ready for Graphify indexing.
  • Archive roles: SQLite is the archive database, Obsidian is the human-readable filesystem projection, Graphify is the navigable graph over that projection, and the LLM is an optional reader, never the archivist.
  • Outbox + idempotency: framework-minted (conversation_id, turn_seq, tool_call_ordinal) keys, retries with backoff, dead-letter queue.
  • Plugin framework (18 in-tree plugins — see Plugins shipped): script-tier (Rhai) + subprocess-tier (JSON-RPC), full manifest schema (tools / transports / identity providers / OAuth / sidecars / admin routes / webhook routes / UI panels / skills).
  • Five shipped transports: Signal (signal-cli sidecar), WhatsApp (wuzapi sidecar), Slack (multi-workspace Socket Mode OAuth), Discord (multi-guild Gateway WebSocket), SMS (Android-gateway WebSocket).
  • HTTP integrations: Google Apps (Gmail/Calendar/Contacts/Tasks/Drive in one OAuth), Google Places, Open-Meteo (key-less weather), Yahoo Finance (market data), Pushover.
  • Research subsystem: deep-research plan/gather/synthesize pipeline with retention and per-phase event flow.
  • Always-on child agents: durable agent definitions, role prompts, mailboxes, bounded supervised runs, checkpoints, backoff, pause/resume, and run history at /agents and /api/admin/agents.
  • SPA: chat-first sidebar, pinned Control thread (every controller-channel message collapses here), token streaming, approval queue, per-plugin admin panels, settings. i18n out of the box — 8 languages (EN / ES / FR / DE / IT / NL / PL / PT), browser-locale auto-detection on first run, language switcher in the setup wizard and persisted to localStorage. See Internationalisation (i18n) below.
  • Native desktop bundles for all three desktops — .app/.dmg on macOS (Apple Silicon, SMAppService LaunchAgent), NSIS .exe on Windows (SCM service), .deb on Linux (systemd --user unit). Each ships a tray icon, the bundled control plane, and the same SPA on 127.0.0.1:3031. See Desktop installations below and docs/desktop-installations.md for the full cross-OS reference.

See docs/architecture.md §18 for the full milestone breakdown.

Plugins shipped

All 18 in-tree plugins ship as ZIPs under dist/ and install via the SPA's Settings → Plugins page (or POST /api/admin/plugins/install). Source under plugins/.

PluginVersionTierKindWhat it does
signal0.5.0scripttransportSignal Messenger via a supervised signal-cli sidecar. Inbound consumer + outbound + group ops + QR/number pairing.
whatsapp0.2.14scripttransportWhatsApp Multi-Device via a supervised wuzapi (whatsmeow-backed) sidecar. Event-driven inbound import, refreshed direct and group history, group ops, attachments, read receipts.
slack0.3.2scripttransportMulti-workspace Slack via Socket Mode (no public URL). Sidecar-free — pure-Rhai over http_post + ws_subscribe + ws_send.
discord0.2.0scripttransportDiscord bot via the Gateway WebSocket. Multi-guild from one bot token, sidecar-free, gateway heartbeats over ws_set_keepalive.
sms-socket0.2.0scripttransportSMS / MMS via the Android SMS Socket app — WebSocket to the operator's phone on LAN.
google-apps0.3.0scriptintegration + identityGmail + Calendar + Contacts + Tasks + Drive in one OAuth grant. Per-module toggle. Identity provider for email/phone via the People API.
google-places0.2.0scriptintegrationGoogle Places (New) API — text search, nearby search, place details. API-key only, no OAuth.
open-meteo0.5.1scriptintegrationKey-less weather, marine, air-quality, seasonal, ensemble, flood, climate, geocoding, elevation via the public Open-Meteo APIs.
finance-yahoo0.2.1scriptintegrationReal-time + historical market data via Yahoo Finance's public quote / chart endpoints. No API key.
pushover0.2.0scriptnotifierOne-way Pushover push notifications to the operator's phone.
identity-local-address-book0.1.0subprocessidentityLocal JSON contact list at ~/.execlaw/contacts.json — auto-trusts saved contacts as KnownTrusted.
hello0.1.0subprocessreferenceEcho tool exercising the subprocess JSON-RPC tier. Template for new plugin authors.
autoresearch0.1.0scriptresearchMulti-step query decomposition, parallel web-scraper fan-out, and synthesis workflow.
python-sandbox0.1.0scriptexecutionPersistent per-conversation Python execution through a supervised Jupyter Kernel Gateway sidecar.
web-scraper0.1.0scriptintegrationJavaScript-rendered page extraction through a supervised Playwright sidecar.
tool-chain0.1.0scriptorchestrationDeterministic multi-step plans with approval-aware execution implemented by host tools.
humanizer-skills0.1.0scriptskillsWriting-style skills for more natural responses.
obsidian-skills0.1.0scriptskillsObsidian vault workflow and atomic-note skills.

Tools, host-side built-ins, and the manifest schema are documented in docs/plugins.md. Chart rendering (chart.render) is a host-side built-in as of 2026-05-15 — it was previously inside open-meteo.

Building plugin ZIPs

The source directories under plugins/ are not themselves installable uploads. Generate the operator-installable archives under dist/ with the repository packaging scripts. On Windows PowerShell, install the root JavaScript build dependency once, then run:

npm ci --no-audit --no-fund
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\scripts\package-plugins.ps1

On Linux, macOS, WSL, or Git Bash, run:

./scripts/package-plugins.sh

The scripts build declared plugin UI panels and package every in-tree plugin as dist/<plugin-id>-<version>.zip, together with a matching .sha256 checksum. For example, the WhatsApp source in plugins/whatsapp/ produces dist/whatsapp-0.2.14.zip. Upload the generated ZIP through Settings → Plugins or POST /api/admin/plugins/install.


Optional Nexus chat appearance

Choose Settings -> General -> Chat appearance -> Nexus for a source-aware timeline: colored source markers, explicit execlaw response labels, event sequence numbers, and a source selector with previous/next navigation. Navigation highlights matching messages without hiding the surrounding chat. Recorded reply links include a source excerpt and jump to the original message; links to history that is not loaded are disabled rather than guessed.

Classic remains the default and restores the original layout immediately. The appearance preference is saved per browser and synchronized between tabs; it does not change server configuration, trust policy, or transport delivery. Existing tool visibility, streaming, attachments, and reply review controls remain available. Nexus supports light and dark color contexts and narrow screens. New transport identifiers receive a fallback label and stable color; tool sources use the actor metadata supplied by the server. It does not invent MCP connections, agent identities, or relationships absent from message data.

Internationalisation (i18n)

The SPA ships with eight languages built in:

CodeLanguage
enEnglish (the source-of-truth defaults, inline in JSX)
esEspañol
frFrançais
deDeutsch
itItaliano
nlNederlands
plPolski
ptPortuguês

How language gets picked. On first load the SPA checks localStorage["execlaw.preferred-language"]; if absent it falls back to navigator.language (when that's one of the supported codes) and finally to English. The setup wizard renders a compact globe-icon language switcher in the top-right corner so the operator can flip languages before they've committed to anything — the choice is persisted to localStorage and applied to every subsequent visit.

How translations work in the code. English defaults live inline in the React source via t("namespace.key", "English default string") — the same pattern as the upstream business website. Other locale bundles (web/src/locales/<lang>.json) are lazily code-split: only the active language's JSON is fetched. When a key is missing from a non-English bundle, t() silently falls back to the English default, so a partial translation can ship without surfacing empty UI strings. {{var}}-style interpolation works the same on the English path and the translated path.

Implementation reference. Core: web/src/i18n/index.ts (i18next bootstrap, lazy-loader registry, t() helper, useT() / useCurrentLanguage() React hooks). UI: web/src/i18n/LanguageSwitcher.tsx. Locale bundles: web/src/locales/.

Adding a new language. Add the ISO code to SUPPORTED_LANGUAGES in web/src/i18n/index.ts, register a lazy-loader entry in localeLoaders, add an OPTIONS row in web/src/i18n/LanguageSwitcher.tsx, and drop a web/src/locales/<code>.json keyed by the same namespace.key strings the JSX passes to t().

Not yet i18n-ized. Server-side strings (CLI output, log lines, plugin-author-facing error messages) are English-only. The translation surface is the operator-facing SPA UI; the operator talks to the agent in whatever language they want — the LLM handles that end on its own.


Minimum requirements

execlaw is self-hosted by design — there is no SaaS tier, no cloud fallback, and no plan for one. Inference happens on the operator's own hardware against a local OpenAI-compatible endpoint. The hardware floor is set by the LLM you choose to run, not by execlaw itself.

Operating system

PlatformStatusRecommended installService backend
Linux x86_64 (Ubuntu 22.04+, Debian 12+, Mint 21+, Pop_OS! 22.04+)Supportedexeclaw_<v>_amd64.deb (Debian-family desktop) or execlaw install (CLI / non-Debian)systemd --user (.deb) / systemd (CLI)
macOS arm64 (Apple Silicon, M1+)Supportedexeclaw.app menu bar bundlelaunchd via SMAppService
macOS x86_64 (Intel)Supportedexeclaw install (CLI)launchd
Windows 10 / 11 (x86_64, MSVC toolchain)Supportedexeclaw_<v>_x64-setup.exe (NSIS) or execlaw install (CLI / headless)Service Control Manager

The CLI path uses the service-manager crate. For desktop installs the recommended path is the OS-native bundle — .app on Apple Silicon, NSIS .exe on Windows, .deb on Debian-family Linux — each registers the background service through that OS's native API (SMAppService / SCM / systemd --user) so install + uninstall stay self-contained. See Desktop installations. CLI install still works on headless servers (and is the only path on non-Debian Linux and Intel Macs).

GPU / inference acceleration

You need a GPU capable of running the LLM you intend to use. The in-tree default is Qwen3.5-27B-AWQ (~14 GB VRAM for weights + a working KV cache budget for ~8K-token contexts). Two acceleration paths are supported out-of-the-box:

PathHardwareBackendTypical floor
NVIDIA CUDARTX 30-series or newer with ≥16 GB VRAMservice-vllm (vLLM, Docker) or native OllamaRTX 4090 / 3090 / A4000
Intel Arc / XeonArc A770 / B580, Battlemage, Xeon w/ AMXservice-openarc (OpenVINO, Docker) or native OllamaArc A770 16 GB
Apple SiliconM1 / M2 / M3 / M4 with 16+ GB unified memorynative Ollama subprocess (Metal)M2 / M3 base 16 GB

CPU-only inference is technically possible via llama.cpp or similar sidecars, but at 27B-AWQ the latency makes the agent loop unusable. Smaller models (Qwen2.5-7B-AWQ at ~5 GB VRAM) work on consumer 8 GB cards if you accept the quality drop — operators swap the model spec in Settings → Backends.

The voice subsystem (Whisper STT, Kokoro TTS) runs alongside the LLM — add ~1-2 GB VRAM headroom if you want both on the same card. Operators with a second GPU (typical Intel-Arc-for-voice + NVIDIA-for-LLM split) can pin each backend per-card via Settings → Runners.

Memory + disk

ResourceFloorComfortable
System RAM16 GB32 GB
Free disk for ~/.execlaw/2 GB10 GB (DB + log retention + plugin sidecar volumes)
Free disk for Docker images30 GB80 GB+ (LLM weights dominate; vLLM + Whisper + Kokoro + plugin sidecars)

Required runtime dependencies

  • Docker — required for per-conversation runner containers, plugin sidecars (signal-cli, wuzapi, …), and managed-mode inference backends. The control plane talks to the local Docker daemon via the standard socket (/var/run/docker.sock on Linux/macOS, \\.\pipe\docker_engine on Windows). Docker Desktop is fine on macOS/Windows; Docker Engine or Podman-with-the-docker-socket-shim works on Linux. Without Docker the agent loop runs text-only with the runner in-process; sidecars and managed inference are unavailable — usable for plain chat but not for the bridged-transport plugins. Apple Silicon exception: Docker Desktop on a Mac runs Linux in a microVM with no Metal access, so containerised inference on M-series GPUs falls back to CPU and is unusable. execlaw spawns Ollama as a native subprocess on Apple Silicon instead — see docs/setup-mac.md. Docker is still needed for the bridged-transport sidecars (signal-cli, wuzapi). Cross-OS Ollama support: the native-subprocess path also works on Linux and Windows when ollama is installed on the host. The setup wizard discovers it automatically and offers it as an alternative serving method alongside vLLM / OpenVINO. See docs/ollama.md for when to pick which.
  • An NVIDIA or Intel GPU driver stack matching the inference path you choose — CUDA 12+ runtime for NVIDIA, the OpenVINO drivers for Intel. Both are normally installed alongside the GPU; execlaw doctor prints what's missing.
  • An OS keyring backend for vault master-key storage — Keychain on macOS, Credential Manager on Windows, Secret Service / KWallet on Linux. The vault falls back to ~/.execlaw/master.key if the keyring is unavailable; the file fallback is also the durable sink on Windows where Credential Manager has documented drift issues (see docs/security.md §5).

Build-from-source dependencies

Only required if you're compiling rather than installing a release binary:

  • Rust 1.85+ (edition 2024). MSRV documented at the workspace root; CI runs against current stable.
  • Node.js 20+ for the SPA build (web/).
  • A C toolchain for the SQLite bundling: gcc/clang on Linux/macOS, MSVC on Windows.
  • Strawberry Perl 5.32+ on Windows only if you build the production sqlcipher feature (vendored OpenSSL needs Perl). Not required for default bundled-sqlite-plain dev builds.

execlaw doctor runs preflight checks for all of the above and prints remediation pointers per platform.


Quick start (production)

execlaw's control plane runs as a host service on bare metal — systemd on Linux, launchd on macOS, the Service Control Manager on Windows. The control plane itself is a single native binary; Docker is required only for the things the control plane spawns out (per- conversation runner containers, plugin sidecars like signal-cli / wuzapi, managed-mode inference backends). On a host without Docker the agent loop still works text-only with the runner running in-process; sidecars and managed inference are unavailable.

One-shot install

cargo install --path crates/cli   # or `cargo build --release` and copy the binary
execlaw install                   # migrate DB → register service → start it
curl http://127.0.0.1:3031/api/health    # → {"status":"ok"}
open  http://127.0.0.1:3031/api/docs     # Swagger + AsyncAPI

execlaw install registers a per-user service by default. Add --system for a system-wide install (root / Administrator). On Windows the Service Control Manager always runs system-level, so --system is implied.

Desktop installations

For desktop hosts the recommended path is the OS-native bundle. Each one ships a tray / menu-bar icon plus the same bundled control plane, and each registers the background service through that OS's native API so install + uninstall stay self-contained. Full cross-OS reference: docs/desktop-installations.md.

macOS (Apple Silicon) — menu bar .app

Registers a LaunchAgent through Apple's modern SMAppService API, so dragging the .app to the Trash automatically removes the background service — no leftover plist in ~/Library/LaunchAgents/.

  1. Download execlaw_<version>_aarch64.dmg from Releases.
  2. Open .dmg → drag execlaw to /Applications.
  3. First launch: right-click execlaw → Open (the build is unsigned, so a plain double-click hits Gatekeeper). macOS remembers the exception.
  4. macOS surfaces Background Items Added the first time — that's SMAppService registering the LaunchAgent. Approve in System Settings → General → Login Items & Extensions if prompted (the tray's status row links you there).
  5. Menu bar icon → Open execlaw → SPA loads on http://127.0.0.1:3031/. First-run wizard takes it from there.

The menu bar also exposes Restart service, Open data folder, View logs (log stream)…, and Uninstall execlaw… (the latter deregisters the LaunchAgent and optionally wipes ~/.execlaw/ before you drag the .app to Trash).

Windows 10 / 11 — NSIS .exe installer

Registers a Service Control Manager service running as LocalSystem so the control plane starts at boot.

  1. Download execlaw_<version>_x64-setup.exe from Releases.
  2. Run the installer; UAC fires (the SCM service install needs admin). NSIS's post-install hook calls execlaw.exe service install --system + service start --system.
  3. SmartScreen warns "Windows protected your PC" on first run (unsigned installer) → More info → Run anyway.
  4. Notification-area icon appears. Open execlaw opens a WebView2 window on http://127.0.0.1:3031/.

Uninstall via Settings → Apps → execlaw → Uninstall (NSIS's pre-uninstall hook stops + deregisters the service) or from the tray's Uninstall execlaw… (UAC → service uninstall).

Linux (Debian / Ubuntu / Mint / Pop_OS!) — .deb

Registers a systemd --user unit on first tray-app launch. No service registration happens at apt install time — apt's postinst runs as root, but systemd --user units must live in the operator's HOME to start under their UID.

  1. Download execlaw_<version>_amd64.deb from Releases.
  2. sudo apt install ./execlaw_<version>_amd64.deb.
  3. Launch execlaw-tray from the application menu (or /usr/bin/execlaw-tray from a shell). The tray calls execlaw service install --user then service start --user.
  4. SNI tray icon appears (Just Works on KDE Plasma, XFCE, MATE, Cinnamon, elementary OS; vanilla GNOME needs the AppIndicator and KStatusNotifierItem Support extension — Ubuntu bundles it since 22.04).
  5. Open execlaw → webkit2gtk-4.1 window on http://127.0.0.1:3031/.

For boot-time start without an interactive login, run loginctl enable-linger $USER once. To uninstall cleanly: tray Uninstall execlaw… first (deregisters the user unit), then sudo apt remove execlaw for the program files.

Building from source

OSCommandToolchain ref
macOS./scripts/build-mac.shdesktop-macos/README.md
Windows./scripts/build-windows.ps1desktop-windows/README.md
Linux./scripts/build-linux.shdesktop-linux/README.md

See docs/desktop-installations.md for the cross-OS architecture reference, docs/architecture.md for the broader desktop-wrapper design, and CONTRIBUTING.md → Cutting a release for the tag → GitHub Release flow.

Service lifecycle

CommandWhat it does
execlaw installFirst-run: migrate + register + start
execlaw service installRegister (without starting)
execlaw service startStart the service
execlaw service restartStop + start
execlaw service stopStop the service
execlaw service statusPrint install state + per-OS log commands
execlaw service uninstallDeregister
execlaw doctorPreflight checks (DB, vault, optional Docker)
execlaw serveRun in the foreground (dev / debug)

Windows Service Notes

  • SPA required: Build the SPA before packaging or reinstalling the server. If web/dist is missing the root will return "execlaw SPA bundle not found." Run:
npm --prefix web ci
npm --prefix web run build
  • Avoid port conflicts: Ensure no foreground server (or other process) binds 127.0.0.1:3031 before starting the installed service; otherwise the service will fail with an OS error (only one usage of each socket address — os error 10048).

  • Elevated install & file-locks: Service install and cargo install --path crates/cli require elevation. If cargo install fails with "Access is denied (os error 5)", stop the service and any running execlaw processes before reinstalling:

# Run as Administrator
Stop-Service -Name execlaw -Force
Get-Process -Name execlaw -ErrorAction SilentlyContinue | Stop-Process -Force
cargo install --path crates/cli
Start-Service -Name execlaw
  • Helper script: A convenience elevated helper was added at scripts/install-elevated.ps1 to stop the service, remove the old binary, run cargo install, and restart the service. Use an elevated PowerShell to run it.

  • Debugging: To diagnose service start failures, run the service command interactively to capture stdout/stderr:

powershell -NoProfile -Command "& 'C:\Users\<you>\\.cargo\\bin\\execlaw.exe' service run --db 'C:\Users\<you>\\.execlaw\\execlaw.db'"
Get-WinEvent -FilterHashtable @{LogName='System'; StartTime=(Get-Date).AddHours(-1)} | Where-Object { $_.Message -match 'execlaw' }

cargo bootstrap, cargo start, cargo stop, cargo restart, cargo svc-status, and cargo doctor are convenience aliases that forward to the equivalent execlaw … invocations (see .cargo/config.toml).

Live logs

OSCommand
Linux (user)journalctl --user -u execlaw -f
Linux (system)journalctl -u execlaw -f
macOSlog stream --predicate 'process == "execlaw"'
WindowsGet-EventLog -Source execlaw -LogName Application

execlaw service status prints the right command for your platform.

First-run setup

curl -X POST http://127.0.0.1:3031/api/setup \
  -H 'content-type: application/json' \
  -d '{"admin_password":"pick-something-longer"}'

The SPA at http://127.0.0.1:3031/ will guide you through the rest (backend wizard, plugin install, personality, etc.).

Always-on child agents

Open /agents after setup to create a specialist agent. Each definition stores its identity, role prompt, backend/model selection, tool metadata, trust policy, cadence, token/runtime budgets, and concurrency limit in SQLite. The host service starts the agent supervisor automatically; it claims due work, processes durable mailbox messages, persists checkpoints and outputs, retries failures with backoff, and resumes scheduling after a restart. The page also provides pause/resume, controller-to-agent messages, and per-agent run history with failure details.

The controller API exposes the same lifecycle at /api/admin/agents: POST creates a definition, GET lists or reads definitions, PUT updates, POST /:id/pause|resume controls execution, POST /:id/messages enqueues mail, and GET /:id/runs reads durable run history. Configure a local inference backend first; agents use the configured Standard backend unless their definition selects another supported purpose.


Dev mode (hot-reload, full stack)

Two long-running processes give you a restart-free edit cycle for both the Rust server and the SPA.

One-time setup

# Rust file-watcher.
cargo install cargo-watch --locked

# SPA dependencies.
cd web && npm install

Run both terminals

# Terminal 1 — Rust hot-reload. cargo-watch rebuilds + restarts the
# binary on every .rs save. Wraps `cargo run -p execlaw -- serve`.
bash scripts/dev-server.sh         # POSIX / WSL / Git Bash on Windows
# or:
pwsh scripts/dev-server.ps1        # Windows PowerShell
# or, from inside web/:
cd web && npm run dev:server       # alias for the bash script

# Terminal 2 — SPA hot-reload. Vite HMR; proxies /api → :3031.
cd web && npm run dev

Open http://127.0.0.1:5173/ — the SPA hits the Vite dev server, which proxies API calls to the cargo-watch'd Rust binary on :3031. Editing a .tsx file triggers a Vite HMR push; editing a .rs file triggers a cargo build + binary restart and the next API call hits the new code (typically <5s for incremental edits).

The dev server, the installed production service, and the Vite proxy all default to 127.0.0.1:3031 — there's no port-swizzling between modes. Override for one-off testing:

EXECLAW_DEV_BIND=127.0.0.1:9000 bash scripts/dev-server.sh
VITE_API_TARGET=http://127.0.0.1:9000 npm run dev

Useful npm scripts (in web/)

ScriptWhat it does
npm run devVite dev server with HMR on :5173. Proxies /api → :3031.
npm run dev:serverForwards to bash ../scripts/dev-server.sh so you can launch the Rust server from inside web/.
npm run buildProduction SPA bundle (web/dist/).
npm run previewServe the built bundle locally.
npm test / npm run test:watchVitest.
npm run linttsc --noEmit.
npm run sizePrint bundle-size budget snapshot.

Rust dev cheatsheet

# Plaintext SQLite path (fast; skips OpenSSL vendoring).
cargo test --workspace
cargo run -p execlaw -- doctor

# Full SQLCipher path (production build).
cargo test --workspace --no-default-features -F execlaw-core/sqlcipher

# Replay a turn — reconstructs the exact prompt, capability set,
# policy decision, and committed events for one conversation/seq.
cargo run -p execlaw -- replay <conversation_id> --at <seq>

Requires Rust 1.85+ (edition 2024). Bare-metal targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, aarch64-apple-darwin. Intel Macs (x86_64-apple-darwin) are explicitly not supported — the only macOS-specific code path that matters is Metal-accelerated inference via Ollama, which lives on Apple Silicon. Service registration on each supported target is handled by the service-manager crate.

Disk-space note

The Rust workspace's target/ directory grows quickly (40+ GB on a warm dev box). If cargo-watch rebuilds start failing with No space left on device, run cargo clean to reclaim.

Graphify integration

execlaw includes a local Graphify knowledge-graph pipeline. The sync script derives web/src/generated/graphifyPreview.json from graphify-out/graph.json, but the current SPA does not import or render that preview artifact.

For Docker/TrueNAS installation, including Python/Graphify image setup, bind-mount permissions, Ollama connectivity, and the optional SPA preview sync, see docs/truenas-docker.md.

Install Graphify (Windows)

& "$HOME\.local\bin\uv.exe" tool install "graphifyy[sql]" --python 3.12
& "$HOME\.local\bin\uv.exe" tool update-shell
& "$HOME\.local\bin\graphify.exe" --version

Open a new terminal before calling graphify by name. The SQL extra indexes the SQLite migrations. The repository already has agent instructions; installing the CLI does not require replacing them or installing Git hooks.

Build the graph and wiki

PowerShell note: use graphify . (no leading slash).

# full semantic extraction + wiki (requires backend)
$env:OLLAMA_API_KEY = "local"
$env:OLLAMA_MODEL = "qwen3.5:9b"
& "$HOME\.local\bin\graphify.exe" . --wiki --backend ollama

# local AST-only fallback (no API keys)
& "$HOME\.local\bin\graphify.exe" update .
& "$HOME\.local\bin\graphify.exe" cluster-only . --no-label

Sync UI preview artifacts

After regenerating graphify-out/graph.json, run:

node scripts/graphify_sync_preview.mjs

This writes:

  • web/src/generated/graphifyPreview.json (lightweight graph slice used by the SPA)
  • graphify-out/wiki/index.md (local wiki scaffold)

Toggle in settings

Operators can enable/disable the welcome-screen graph at:

  • Settings -> General -> Show Graphify preview on New chat

The toggle is stored per browser in localStorage (execlaw.chat.graphify_welcome_visible).

Built-in Graphify tool (for local models)

execlaw now exposes a built-in model tool named graphify.

  • Visible at Settings -> Tools as graphify
  • Registered at server boot and synced into tool-access policy
  • Controller-only by default

This means local Ollama-backed models can call Graphify directly during tool-use turns (instead of replying that no graphify tool exists).

Example tool args:

{"action":"build","target_path":".","wiki":true,"backend":"ollama","model":"qwen3.5:9b"}
{"action":"query","question":"How does inbound transport reach TurnExecutor?"}
{"action":"path","from":"crates/server/src/chats.rs","to":"crates/runner-local/src/turn.rs"}

Optional override for executable location:

  • env: EXECLAW_GRAPHIFY_BIN (defaults to graphify)

Built-in Graphiti tool + admin API

execlaw now exposes a built-in model tool named graphiti for temporal-memory search and durable ingestion via a Graphiti-compatible HTTP service.

  • Visible at Settings -> Tools as graphiti
  • Registered at server boot and synced into tool-access policy
  • Default allowed trust class: Controller

Configuration is stored in SQLite and the credential value is stored in the core vault. There is no production environment-variable fallback.

  • PUT /api/admin/graphiti/config accepts base_url, optional api_key_vault_ref, and a write-only optional api_key
  • GET /api/admin/graphiti/config returns the endpoint, credential reference, and whether the referenced secret exists; it never returns the secret value

Admin validation endpoints (auth required):

  • GET /api/admin/graphiti/health
  • POST /api/admin/graphiti/test-call with body { "args": { ...tool args... } }

Example test-call body:

{
   "args": {
      "action": "search",
      "query": "find policy",
      "top_k": 5
   }
}

Obsidian lessons pipeline

The workspace now includes a local .obsidian/ lessons pipeline for persistent, deduped memory notes (Patterns, Mistakes, Decisions, Context) plus a review-only weekly maintenance report.

Scaffold + validation:

pwsh -File scripts/verify_copilot_obsidian_pipeline.ps1 -VaultDir .obsidian

Import lessons from a transcript and regenerate index/report:

pwsh -File scripts/sync_copilot_obsidian.ps1 -VaultDir .obsidian -TranscriptPath <path-to-transcript.jsonl>

One-command maintenance task (repo root):

npm run graph-memory:maintain

That command runs:

  • graphify update . (if Graphify CLI is installed)
  • node scripts/graphify_sync_preview.mjs
  • python scripts/weekly_lessons_maintenance_report.py --vault-dir .obsidian --stale-days 30

Optional auto-start hook (post-commit)

Install a git post-commit hook that runs only when structural files changed. When triggered, it runs the same maintenance script above.

Install it once:

pwsh -File scripts/install_graphify_memory_hook.ps1

Hook runner script:

  • scripts/post_commit_graphify_memory.ps1

Workspace layout

PathPurpose
crates/core/Event log, FSM, migrations (flattened baseline + incremental), SQLCipher-encrypted storage, principal store, memory lifecycle.
crates/session/Per-conversation pipeline composition (text vs voice).
crates/inference-api/OpenAI-compatible LLM client. No cloud SDKs.
crates/model-adapter/Provider-specific prompt + tool-call shape adapters (Qwen, Llama, OpenAI-compatible variants).
crates/runner-local/TurnExecutor — full tool-loop turn path.
crates/runner-protocol/Wire types for the per-conversation runner-container RPC.
crates/runner-binary/Static-musl execlaw-runner binary baked into Dockerfile.runner.
crates/voice-pipeline/STT → LLM → TTS two-lane Tokio graph.
crates/plugin-sdk/plugin.toml manifest parser + ZIP staging.
crates/plugin-host/Plugin registry + lifecycle (install / enable / disable / hydrate / purge).
crates/script/Embedded Rhai engine + primitive bindings (HTTP, sidecar, vault, OAuth, WS, routing, JSON, time).
crates/skills/Skills runtime (capture, retrieve, surface in prompt).
crates/charting/Server-side chart rendering for the chart.render host built-in.
crates/container-manager/bollard client + tiered hardware detection.
crates/policy/Rule of Two, capability tokens, input guards, spotlighting.
crates/vault/OS-keyring master key + Argon2id admin password.
crates/transport-api/Trait a transport plugin implements.
crates/identity-api/Trait an identity-provider plugin implements.
crates/outbox/Outbox relay primitives (idempotency, retry, dead-letter).
crates/server/Axum HTTP + WebSocket surface, sidecar supervisor, admin/webhook routers, chat path, SPA-embed via rust-embed.
crates/mcp-client/MCP server registration + tool dispatch (alternative to plugin tools).
crates/cli/execlaw binary (install, service, doctor, serve, replay, eval, …).
crates/eval-harness/LLM-judge harness against local Qwen.
plugins/In-tree reference + first-party plugins (see Plugins shipped).
web/React + react-bootstrap SPA. Vite + Vitest.
desktop-macos/Tauri 2 menu bar app for Apple Silicon. SMAppService LaunchAgent + WKWebView. Out-of-workspace cargo crate.
desktop-windows/Tauri 2 tray app for x86_64 Windows. NSIS installer + SCM service + WebView2. Out-of-workspace cargo crate.
desktop-linux/Tauri 2 tray app for x86_64 Debian-family Linux. .deb installer + systemd --user unit + webkit2gtk-4.1. Out-of-workspace cargo crate.
scripts/dev-server.{sh,ps1} (cargo-watch wrappers), build-mac.sh / build-windows.ps1 / build-linux.sh (Tauri releases), trace-turn.{sh,ps1} (turn replay).
docs/Architecture + agent-model + plugins + setup walkthroughs + desktop-installations + ollama + screenshots.
evals/Rubric TOML files for the LLM-judge harness.
spec/OpenAPI + AsyncAPI specs.
dist/Built plugin install ZIPs (one per plugin / version).
.github/workflows/CI (per-push), macos-bundle.yml / windows-bundle.yml / linux-bundle.yml (tag-driven .app+.dmg / NSIS .exe / .deb → GitHub Releases).

License

Apache License, Version 2.0 — see LICENSE and NOTICE.

Copyright (c) 2026 Justin Long.

Contributors

nikolacucuk

72 commits

nikolacucuk/execlaw

Rust

0

79 commits

updated Sep 18, 2026

See the code

README

execlaw

CI codecov License: Apache 2.0

Self-hosted Rust agent framework with persistent memory, hook-based plugins, tools, and skills. Bare metal best metal. All inference runs on operator hardware.

execlaw — Skills page execlaw — Deep research session

Documentation

DocWhat it covers
docs/architecture.mdSystem topology, design principles, FSM, data model, recovery, observability — the what.
docs/agent-model.mdTurnExecutor, memory layers, reflection loop, planner/executor split — the how of one turn.
docs/memory-roadmap.mdGoverned memory assets, task/agent loadouts, hybrid retrieval, local Wiki, and CodeGraph implementation checklist.
docs/plugins.mdPlugin manifest schema, runtime tiers, sidecar model, Rhai primitives, and a step-by-step guide for writing a custom plugin.
docs/operator-decision-rubric.mdStructured rubric for placing features in plugins vs MCP vs host core, plus tool-chaining and learning-loop guidance.
docs/hermes-porting-todo.mdHistorical completion checklist for Hermes-originated capabilities ported into execlaw.
docs/setup-walkthroughs.mdOperator-facing pairing flows for Signal QR, WhatsApp wuzapi, Slack OAuth, Google OAuth + API-key.
docs/desktop-installations.mdCross-OS reference for the three desktop bundles — .app/.dmg, NSIS .exe, .deb. Tray architecture, service-manager mapping, install + uninstall flows, build scripts.
docs/ollama.mdPre-installed Ollama support across macOS / Linux / Windows. How discovery works, when to pick Ollama over Docker, the wizard's serving dropdown.
docs/copilot-graphify-obsidian-workspace-setup.mdStep-by-step guide to reproduce Graphify + Obsidian memory workflow in other repositories using GitHub Copilot.
docs/setup-mac.mdApple Silicon first-run notes — native Ollama subprocess, model sizing, brand indicator.
docs/truenas-docker.mdTrueNAS SCALE Docker deployment with persistent ZFS storage and external Ollama.
desktop-macos/README.mdmacOS .app bundle internals — Tauri 2, SMAppService, build script.
desktop-windows/README.mdWindows NSIS .exe bundle internals — Tauri 2, SCM service, build script.
desktop-linux/README.mdLinux .deb bundle internals — Tauri 2, systemd --user unit, build script.
docs/security.mdDisclosure path, threat model, cryptography, trust assumptions, known limitations, hardening checklist.
docs/security-hardening-2026-06.md2026-06 hardening pass: HTTP security headers, login rate limiting, expanded homoglyph coverage, webhook auth enforcement.
docs/sidecar-supervisor-design.mdSupervised-container layer plugins compose against.
docs/runner-design.mdPer-conversation runner container model.
docs/voice-followups.mdVoice modality design notes.
CONTRIBUTING.mdWorkflow, code conventions, AGPL→Apache-2.0 licensing notes.
AGENTS.mdOnboarding for AI coding agents working on this repo.

Developer tooling note (Graphify CLI)

Install the official graphifyy package (double y) with uv:

& "$HOME\.local\bin\uv.exe" tool install "graphifyy[sql]" --python 3.12
& "$HOME\.local\bin\uv.exe" tool update-shell
& "$HOME\.local\bin\graphify.exe" update .

Open a new terminal after PATH changes. graphify update . uses local AST extraction, not an LLM. The root .graphifyignore excludes alternate Cargo build trees and nested graph output. Check the result with graphify query "MessageStream" or graphify path MessageStream useChatAppearance.

Windows terminal and validation

The workspace VS Code terminal profile binds Ctrl+U to BackwardDeleteLine. Without that binding, line-clear input can appear as a literal ^U and break otherwise valid commands. Open a new terminal to apply the profile. Existing terminals can run Set-PSReadLineKeyHandler -Chord Ctrl+u -Function BackwardDeleteLine.

Execution policy is unchanged. Use npm.cmd when Restricted policy blocks npm.ps1. Tasks use a separate automation profile to avoid colliding with interactive startup arguments. Tasks: Run Task -> web: verify runs tests, type-check, build, size budgets, browser checks, and dependency audit.

Install Chromium once before browser checks:

node web/node_modules/@playwright/test/cli.js install chromium
npm.cmd --prefix web run build
npm.cmd --prefix web run test:appearance

The browser check captures dark/light screenshots at 1440, 390, and 320 pixels in a printed temporary directory. It checks source navigation, Classic restoration, streaming, and production bundle boot with mocked API responses. Its loopback servers and browser close after the test. It never starts the operator backend, accesses the vault, or sends real transport messages.

What ships today

  • Control plane (Rust binary): event log + scheduler + plugin host + container manager + outbox relay + axum server + SQLCipher vault.
  • Per-conversation runner containers: stateless against the log, stateless OpenAI-compatible client to local inference (vLLM / OpenArc / Whisper / Kokoro).
  • Trust ladder + Rule of Two: Controller / Delegated / KnownTrusted / KnownLimited / UnknownPending / Blocked with cold-contact escalation, signed approval-token JWTs, sideband HITL.
  • HMAC-chained event log: every committed row is tamper-evident; replay rebuilds state deterministically.
  • Deterministic social archive: transport messages are retained in SQLite and mirrored as Obsidian-compatible Markdown under .obsidian/archive/<transport>/<group|direct>/, without LLM calls; SQLite FTS5 provides exact search and the files are ready for Graphify indexing.
  • Archive roles: SQLite is the archive database, Obsidian is the human-readable filesystem projection, Graphify is the navigable graph over that projection, and the LLM is an optional reader, never the archivist.
  • Outbox + idempotency: framework-minted (conversation_id, turn_seq, tool_call_ordinal) keys, retries with backoff, dead-letter queue.
  • Plugin framework (18 in-tree plugins — see Plugins shipped): script-tier (Rhai) + subprocess-tier (JSON-RPC), full manifest schema (tools / transports / identity providers / OAuth / sidecars / admin routes / webhook routes / UI panels / skills).
  • Five shipped transports: Signal (signal-cli sidecar), WhatsApp (wuzapi sidecar), Slack (multi-workspace Socket Mode OAuth), Discord (multi-guild Gateway WebSocket), SMS (Android-gateway WebSocket).
  • HTTP integrations: Google Apps (Gmail/Calendar/Contacts/Tasks/Drive in one OAuth), Google Places, Open-Meteo (key-less weather), Yahoo Finance (market data), Pushover.
  • Research subsystem: deep-research plan/gather/synthesize pipeline with retention and per-phase event flow.
  • Always-on child agents: durable agent definitions, role prompts, mailboxes, bounded supervised runs, checkpoints, backoff, pause/resume, and run history at /agents and /api/admin/agents.
  • SPA: chat-first sidebar, pinned Control thread (every controller-channel message collapses here), token streaming, approval queue, per-plugin admin panels, settings. i18n out of the box — 8 languages (EN / ES / FR / DE / IT / NL / PL / PT), browser-locale auto-detection on first run, language switcher in the setup wizard and persisted to localStorage. See Internationalisation (i18n) below.
  • Native desktop bundles for all three desktops — .app/.dmg on macOS (Apple Silicon, SMAppService LaunchAgent), NSIS .exe on Windows (SCM service), .deb on Linux (systemd --user unit). Each ships a tray icon, the bundled control plane, and the same SPA on 127.0.0.1:3031. See Desktop installations below and docs/desktop-installations.md for the full cross-OS reference.

See docs/architecture.md §18 for the full milestone breakdown.

Plugins shipped

All 18 in-tree plugins ship as ZIPs under dist/ and install via the SPA's Settings → Plugins page (or POST /api/admin/plugins/install). Source under plugins/.

PluginVersionTierKindWhat it does
signal0.5.0scripttransportSignal Messenger via a supervised signal-cli sidecar. Inbound consumer + outbound + group ops + QR/number pairing.
whatsapp0.2.14scripttransportWhatsApp Multi-Device via a supervised wuzapi (whatsmeow-backed) sidecar. Event-driven inbound import, refreshed direct and group history, group ops, attachments, read receipts.
slack0.3.2scripttransportMulti-workspace Slack via Socket Mode (no public URL). Sidecar-free — pure-Rhai over http_post + ws_subscribe + ws_send.
discord0.2.0scripttransportDiscord bot via the Gateway WebSocket. Multi-guild from one bot token, sidecar-free, gateway heartbeats over ws_set_keepalive.
sms-socket0.2.0scripttransportSMS / MMS via the Android SMS Socket app — WebSocket to the operator's phone on LAN.
google-apps0.3.0scriptintegration + identityGmail + Calendar + Contacts + Tasks + Drive in one OAuth grant. Per-module toggle. Identity provider for email/phone via the People API.
google-places0.2.0scriptintegrationGoogle Places (New) API — text search, nearby search, place details. API-key only, no OAuth.
open-meteo0.5.1scriptintegrationKey-less weather, marine, air-quality, seasonal, ensemble, flood, climate, geocoding, elevation via the public Open-Meteo APIs.
finance-yahoo0.2.1scriptintegrationReal-time + historical market data via Yahoo Finance's public quote / chart endpoints. No API key.
pushover0.2.0scriptnotifierOne-way Pushover push notifications to the operator's phone.
identity-local-address-book0.1.0subprocessidentityLocal JSON contact list at ~/.execlaw/contacts.json — auto-trusts saved contacts as KnownTrusted.
hello0.1.0subprocessreferenceEcho tool exercising the subprocess JSON-RPC tier. Template for new plugin authors.
autoresearch0.1.0scriptresearchMulti-step query decomposition, parallel web-scraper fan-out, and synthesis workflow.
python-sandbox0.1.0scriptexecutionPersistent per-conversation Python execution through a supervised Jupyter Kernel Gateway sidecar.
web-scraper0.1.0scriptintegrationJavaScript-rendered page extraction through a supervised Playwright sidecar.
tool-chain0.1.0scriptorchestrationDeterministic multi-step plans with approval-aware execution implemented by host tools.
humanizer-skills0.1.0scriptskillsWriting-style skills for more natural responses.
obsidian-skills0.1.0scriptskillsObsidian vault workflow and atomic-note skills.

Tools, host-side built-ins, and the manifest schema are documented in docs/plugins.md. Chart rendering (chart.render) is a host-side built-in as of 2026-05-15 — it was previously inside open-meteo.

Building plugin ZIPs

The source directories under plugins/ are not themselves installable uploads. Generate the operator-installable archives under dist/ with the repository packaging scripts. On Windows PowerShell, install the root JavaScript build dependency once, then run:

npm ci --no-audit --no-fund
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\scripts\package-plugins.ps1

On Linux, macOS, WSL, or Git Bash, run:

./scripts/package-plugins.sh

The scripts build declared plugin UI panels and package every in-tree plugin as dist/<plugin-id>-<version>.zip, together with a matching .sha256 checksum. For example, the WhatsApp source in plugins/whatsapp/ produces dist/whatsapp-0.2.14.zip. Upload the generated ZIP through Settings → Plugins or POST /api/admin/plugins/install.


Optional Nexus chat appearance

Choose Settings -> General -> Chat appearance -> Nexus for a source-aware timeline: colored source markers, explicit execlaw response labels, event sequence numbers, and a source selector with previous/next navigation. Navigation highlights matching messages without hiding the surrounding chat. Recorded reply links include a source excerpt and jump to the original message; links to history that is not loaded are disabled rather than guessed.

Classic remains the default and restores the original layout immediately. The appearance preference is saved per browser and synchronized between tabs; it does not change server configuration, trust policy, or transport delivery. Existing tool visibility, streaming, attachments, and reply review controls remain available. Nexus supports light and dark color contexts and narrow screens. New transport identifiers receive a fallback label and stable color; tool sources use the actor metadata supplied by the server. It does not invent MCP connections, agent identities, or relationships absent from message data.

Internationalisation (i18n)

The SPA ships with eight languages built in:

CodeLanguage
enEnglish (the source-of-truth defaults, inline in JSX)
esEspañol
frFrançais
deDeutsch
itItaliano
nlNederlands
plPolski
ptPortuguês

How language gets picked. On first load the SPA checks localStorage["execlaw.preferred-language"]; if absent it falls back to navigator.language (when that's one of the supported codes) and finally to English. The setup wizard renders a compact globe-icon language switcher in the top-right corner so the operator can flip languages before they've committed to anything — the choice is persisted to localStorage and applied to every subsequent visit.

How translations work in the code. English defaults live inline in the React source via t("namespace.key", "English default string") — the same pattern as the upstream business website. Other locale bundles (web/src/locales/<lang>.json) are lazily code-split: only the active language's JSON is fetched. When a key is missing from a non-English bundle, t() silently falls back to the English default, so a partial translation can ship without surfacing empty UI strings. {{var}}-style interpolation works the same on the English path and the translated path.

Implementation reference. Core: web/src/i18n/index.ts (i18next bootstrap, lazy-loader registry, t() helper, useT() / useCurrentLanguage() React hooks). UI: web/src/i18n/LanguageSwitcher.tsx. Locale bundles: web/src/locales/.

Adding a new language. Add the ISO code to SUPPORTED_LANGUAGES in web/src/i18n/index.ts, register a lazy-loader entry in localeLoaders, add an OPTIONS row in web/src/i18n/LanguageSwitcher.tsx, and drop a web/src/locales/<code>.json keyed by the same namespace.key strings the JSX passes to t().

Not yet i18n-ized. Server-side strings (CLI output, log lines, plugin-author-facing error messages) are English-only. The translation surface is the operator-facing SPA UI; the operator talks to the agent in whatever language they want — the LLM handles that end on its own.


Minimum requirements

execlaw is self-hosted by design — there is no SaaS tier, no cloud fallback, and no plan for one. Inference happens on the operator's own hardware against a local OpenAI-compatible endpoint. The hardware floor is set by the LLM you choose to run, not by execlaw itself.

Operating system

PlatformStatusRecommended installService backend
Linux x86_64 (Ubuntu 22.04+, Debian 12+, Mint 21+, Pop_OS! 22.04+)Supportedexeclaw_<v>_amd64.deb (Debian-family desktop) or execlaw install (CLI / non-Debian)systemd --user (.deb) / systemd (CLI)
macOS arm64 (Apple Silicon, M1+)Supportedexeclaw.app menu bar bundlelaunchd via SMAppService
macOS x86_64 (Intel)Supportedexeclaw install (CLI)launchd
Windows 10 / 11 (x86_64, MSVC toolchain)Supportedexeclaw_<v>_x64-setup.exe (NSIS) or execlaw install (CLI / headless)Service Control Manager

The CLI path uses the service-manager crate. For desktop installs the recommended path is the OS-native bundle — .app on Apple Silicon, NSIS .exe on Windows, .deb on Debian-family Linux — each registers the background service through that OS's native API (SMAppService / SCM / systemd --user) so install + uninstall stay self-contained. See Desktop installations. CLI install still works on headless servers (and is the only path on non-Debian Linux and Intel Macs).

GPU / inference acceleration

You need a GPU capable of running the LLM you intend to use. The in-tree default is Qwen3.5-27B-AWQ (~14 GB VRAM for weights + a working KV cache budget for ~8K-token contexts). Two acceleration paths are supported out-of-the-box:

PathHardwareBackendTypical floor
NVIDIA CUDARTX 30-series or newer with ≥16 GB VRAMservice-vllm (vLLM, Docker) or native OllamaRTX 4090 / 3090 / A4000
Intel Arc / XeonArc A770 / B580, Battlemage, Xeon w/ AMXservice-openarc (OpenVINO, Docker) or native OllamaArc A770 16 GB
Apple SiliconM1 / M2 / M3 / M4 with 16+ GB unified memorynative Ollama subprocess (Metal)M2 / M3 base 16 GB

CPU-only inference is technically possible via llama.cpp or similar sidecars, but at 27B-AWQ the latency makes the agent loop unusable. Smaller models (Qwen2.5-7B-AWQ at ~5 GB VRAM) work on consumer 8 GB cards if you accept the quality drop — operators swap the model spec in Settings → Backends.

The voice subsystem (Whisper STT, Kokoro TTS) runs alongside the LLM — add ~1-2 GB VRAM headroom if you want both on the same card. Operators with a second GPU (typical Intel-Arc-for-voice + NVIDIA-for-LLM split) can pin each backend per-card via Settings → Runners.

Memory + disk

ResourceFloorComfortable
System RAM16 GB32 GB
Free disk for ~/.execlaw/2 GB10 GB (DB + log retention + plugin sidecar volumes)
Free disk for Docker images30 GB80 GB+ (LLM weights dominate; vLLM + Whisper + Kokoro + plugin sidecars)

Required runtime dependencies

  • Docker — required for per-conversation runner containers, plugin sidecars (signal-cli, wuzapi, …), and managed-mode inference backends. The control plane talks to the local Docker daemon via the standard socket (/var/run/docker.sock on Linux/macOS, \\.\pipe\docker_engine on Windows). Docker Desktop is fine on macOS/Windows; Docker Engine or Podman-with-the-docker-socket-shim works on Linux. Without Docker the agent loop runs text-only with the runner in-process; sidecars and managed inference are unavailable — usable for plain chat but not for the bridged-transport plugins. Apple Silicon exception: Docker Desktop on a Mac runs Linux in a microVM with no Metal access, so containerised inference on M-series GPUs falls back to CPU and is unusable. execlaw spawns Ollama as a native subprocess on Apple Silicon instead — see docs/setup-mac.md. Docker is still needed for the bridged-transport sidecars (signal-cli, wuzapi). Cross-OS Ollama support: the native-subprocess path also works on Linux and Windows when ollama is installed on the host. The setup wizard discovers it automatically and offers it as an alternative serving method alongside vLLM / OpenVINO. See docs/ollama.md for when to pick which.
  • An NVIDIA or Intel GPU driver stack matching the inference path you choose — CUDA 12+ runtime for NVIDIA, the OpenVINO drivers for Intel. Both are normally installed alongside the GPU; execlaw doctor prints what's missing.
  • An OS keyring backend for vault master-key storage — Keychain on macOS, Credential Manager on Windows, Secret Service / KWallet on Linux. The vault falls back to ~/.execlaw/master.key if the keyring is unavailable; the file fallback is also the durable sink on Windows where Credential Manager has documented drift issues (see docs/security.md §5).

Build-from-source dependencies

Only required if you're compiling rather than installing a release binary:

  • Rust 1.85+ (edition 2024). MSRV documented at the workspace root; CI runs against current stable.
  • Node.js 20+ for the SPA build (web/).
  • A C toolchain for the SQLite bundling: gcc/clang on Linux/macOS, MSVC on Windows.
  • Strawberry Perl 5.32+ on Windows only if you build the production sqlcipher feature (vendored OpenSSL needs Perl). Not required for default bundled-sqlite-plain dev builds.

execlaw doctor runs preflight checks for all of the above and prints remediation pointers per platform.


Quick start (production)

execlaw's control plane runs as a host service on bare metal — systemd on Linux, launchd on macOS, the Service Control Manager on Windows. The control plane itself is a single native binary; Docker is required only for the things the control plane spawns out (per- conversation runner containers, plugin sidecars like signal-cli / wuzapi, managed-mode inference backends). On a host without Docker the agent loop still works text-only with the runner running in-process; sidecars and managed inference are unavailable.

One-shot install

cargo install --path crates/cli   # or `cargo build --release` and copy the binary
execlaw install                   # migrate DB → register service → start it
curl http://127.0.0.1:3031/api/health    # → {"status":"ok"}
open  http://127.0.0.1:3031/api/docs     # Swagger + AsyncAPI

execlaw install registers a per-user service by default. Add --system for a system-wide install (root / Administrator). On Windows the Service Control Manager always runs system-level, so --system is implied.

Desktop installations

For desktop hosts the recommended path is the OS-native bundle. Each one ships a tray / menu-bar icon plus the same bundled control plane, and each registers the background service through that OS's native API so install + uninstall stay self-contained. Full cross-OS reference: docs/desktop-installations.md.

macOS (Apple Silicon) — menu bar .app

Registers a LaunchAgent through Apple's modern SMAppService API, so dragging the .app to the Trash automatically removes the background service — no leftover plist in ~/Library/LaunchAgents/.

  1. Download execlaw_<version>_aarch64.dmg from Releases.
  2. Open .dmg → drag execlaw to /Applications.
  3. First launch: right-click execlaw → Open (the build is unsigned, so a plain double-click hits Gatekeeper). macOS remembers the exception.
  4. macOS surfaces Background Items Added the first time — that's SMAppService registering the LaunchAgent. Approve in System Settings → General → Login Items & Extensions if prompted (the tray's status row links you there).
  5. Menu bar icon → Open execlaw → SPA loads on http://127.0.0.1:3031/. First-run wizard takes it from there.

The menu bar also exposes Restart service, Open data folder, View logs (log stream)…, and Uninstall execlaw… (the latter deregisters the LaunchAgent and optionally wipes ~/.execlaw/ before you drag the .app to Trash).

Windows 10 / 11 — NSIS .exe installer

Registers a Service Control Manager service running as LocalSystem so the control plane starts at boot.

  1. Download execlaw_<version>_x64-setup.exe from Releases.
  2. Run the installer; UAC fires (the SCM service install needs admin). NSIS's post-install hook calls execlaw.exe service install --system + service start --system.
  3. SmartScreen warns "Windows protected your PC" on first run (unsigned installer) → More info → Run anyway.
  4. Notification-area icon appears. Open execlaw opens a WebView2 window on http://127.0.0.1:3031/.

Uninstall via Settings → Apps → execlaw → Uninstall (NSIS's pre-uninstall hook stops + deregisters the service) or from the tray's Uninstall execlaw… (UAC → service uninstall).

Linux (Debian / Ubuntu / Mint / Pop_OS!) — .deb

Registers a systemd --user unit on first tray-app launch. No service registration happens at apt install time — apt's postinst runs as root, but systemd --user units must live in the operator's HOME to start under their UID.

  1. Download execlaw_<version>_amd64.deb from Releases.
  2. sudo apt install ./execlaw_<version>_amd64.deb.
  3. Launch execlaw-tray from the application menu (or /usr/bin/execlaw-tray from a shell). The tray calls execlaw service install --user then service start --user.
  4. SNI tray icon appears (Just Works on KDE Plasma, XFCE, MATE, Cinnamon, elementary OS; vanilla GNOME needs the AppIndicator and KStatusNotifierItem Support extension — Ubuntu bundles it since 22.04).
  5. Open execlaw → webkit2gtk-4.1 window on http://127.0.0.1:3031/.

For boot-time start without an interactive login, run loginctl enable-linger $USER once. To uninstall cleanly: tray Uninstall execlaw… first (deregisters the user unit), then sudo apt remove execlaw for the program files.

Building from source

OSCommandToolchain ref
macOS./scripts/build-mac.shdesktop-macos/README.md
Windows./scripts/build-windows.ps1desktop-windows/README.md
Linux./scripts/build-linux.shdesktop-linux/README.md

See docs/desktop-installations.md for the cross-OS architecture reference, docs/architecture.md for the broader desktop-wrapper design, and CONTRIBUTING.md → Cutting a release for the tag → GitHub Release flow.

Service lifecycle

CommandWhat it does
execlaw installFirst-run: migrate + register + start
execlaw service installRegister (without starting)
execlaw service startStart the service
execlaw service restartStop + start
execlaw service stopStop the service
execlaw service statusPrint install state + per-OS log commands
execlaw service uninstallDeregister
execlaw doctorPreflight checks (DB, vault, optional Docker)
execlaw serveRun in the foreground (dev / debug)

Windows Service Notes

  • SPA required: Build the SPA before packaging or reinstalling the server. If web/dist is missing the root will return "execlaw SPA bundle not found." Run:
npm --prefix web ci
npm --prefix web run build
  • Avoid port conflicts: Ensure no foreground server (or other process) binds 127.0.0.1:3031 before starting the installed service; otherwise the service will fail with an OS error (only one usage of each socket address — os error 10048).

  • Elevated install & file-locks: Service install and cargo install --path crates/cli require elevation. If cargo install fails with "Access is denied (os error 5)", stop the service and any running execlaw processes before reinstalling:

# Run as Administrator
Stop-Service -Name execlaw -Force
Get-Process -Name execlaw -ErrorAction SilentlyContinue | Stop-Process -Force
cargo install --path crates/cli
Start-Service -Name execlaw
  • Helper script: A convenience elevated helper was added at scripts/install-elevated.ps1 to stop the service, remove the old binary, run cargo install, and restart the service. Use an elevated PowerShell to run it.

  • Debugging: To diagnose service start failures, run the service command interactively to capture stdout/stderr:

powershell -NoProfile -Command "& 'C:\Users\<you>\\.cargo\\bin\\execlaw.exe' service run --db 'C:\Users\<you>\\.execlaw\\execlaw.db'"
Get-WinEvent -FilterHashtable @{LogName='System'; StartTime=(Get-Date).AddHours(-1)} | Where-Object { $_.Message -match 'execlaw' }

cargo bootstrap, cargo start, cargo stop, cargo restart, cargo svc-status, and cargo doctor are convenience aliases that forward to the equivalent execlaw … invocations (see .cargo/config.toml).

Live logs

OSCommand
Linux (user)journalctl --user -u execlaw -f
Linux (system)journalctl -u execlaw -f
macOSlog stream --predicate 'process == "execlaw"'
WindowsGet-EventLog -Source execlaw -LogName Application

execlaw service status prints the right command for your platform.

First-run setup

curl -X POST http://127.0.0.1:3031/api/setup \
  -H 'content-type: application/json' \
  -d '{"admin_password":"pick-something-longer"}'

The SPA at http://127.0.0.1:3031/ will guide you through the rest (backend wizard, plugin install, personality, etc.).

Always-on child agents

Open /agents after setup to create a specialist agent. Each definition stores its identity, role prompt, backend/model selection, tool metadata, trust policy, cadence, token/runtime budgets, and concurrency limit in SQLite. The host service starts the agent supervisor automatically; it claims due work, processes durable mailbox messages, persists checkpoints and outputs, retries failures with backoff, and resumes scheduling after a restart. The page also provides pause/resume, controller-to-agent messages, and per-agent run history with failure details.

The controller API exposes the same lifecycle at /api/admin/agents: POST creates a definition, GET lists or reads definitions, PUT updates, POST /:id/pause|resume controls execution, POST /:id/messages enqueues mail, and GET /:id/runs reads durable run history. Configure a local inference backend first; agents use the configured Standard backend unless their definition selects another supported purpose.


Dev mode (hot-reload, full stack)

Two long-running processes give you a restart-free edit cycle for both the Rust server and the SPA.

One-time setup

# Rust file-watcher.
cargo install cargo-watch --locked

# SPA dependencies.
cd web && npm install

Run both terminals

# Terminal 1 — Rust hot-reload. cargo-watch rebuilds + restarts the
# binary on every .rs save. Wraps `cargo run -p execlaw -- serve`.
bash scripts/dev-server.sh         # POSIX / WSL / Git Bash on Windows
# or:
pwsh scripts/dev-server.ps1        # Windows PowerShell
# or, from inside web/:
cd web && npm run dev:server       # alias for the bash script

# Terminal 2 — SPA hot-reload. Vite HMR; proxies /api → :3031.
cd web && npm run dev

Open http://127.0.0.1:5173/ — the SPA hits the Vite dev server, which proxies API calls to the cargo-watch'd Rust binary on :3031. Editing a .tsx file triggers a Vite HMR push; editing a .rs file triggers a cargo build + binary restart and the next API call hits the new code (typically <5s for incremental edits).

The dev server, the installed production service, and the Vite proxy all default to 127.0.0.1:3031 — there's no port-swizzling between modes. Override for one-off testing:

EXECLAW_DEV_BIND=127.0.0.1:9000 bash scripts/dev-server.sh
VITE_API_TARGET=http://127.0.0.1:9000 npm run dev

Useful npm scripts (in web/)

ScriptWhat it does
npm run devVite dev server with HMR on :5173. Proxies /api → :3031.
npm run dev:serverForwards to bash ../scripts/dev-server.sh so you can launch the Rust server from inside web/.
npm run buildProduction SPA bundle (web/dist/).
npm run previewServe the built bundle locally.
npm test / npm run test:watchVitest.
npm run linttsc --noEmit.
npm run sizePrint bundle-size budget snapshot.

Rust dev cheatsheet

# Plaintext SQLite path (fast; skips OpenSSL vendoring).
cargo test --workspace
cargo run -p execlaw -- doctor

# Full SQLCipher path (production build).
cargo test --workspace --no-default-features -F execlaw-core/sqlcipher

# Replay a turn — reconstructs the exact prompt, capability set,
# policy decision, and committed events for one conversation/seq.
cargo run -p execlaw -- replay <conversation_id> --at <seq>

Requires Rust 1.85+ (edition 2024). Bare-metal targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, aarch64-apple-darwin. Intel Macs (x86_64-apple-darwin) are explicitly not supported — the only macOS-specific code path that matters is Metal-accelerated inference via Ollama, which lives on Apple Silicon. Service registration on each supported target is handled by the service-manager crate.

Disk-space note

The Rust workspace's target/ directory grows quickly (40+ GB on a warm dev box). If cargo-watch rebuilds start failing with No space left on device, run cargo clean to reclaim.

Graphify integration

execlaw includes a local Graphify knowledge-graph pipeline. The sync script derives web/src/generated/graphifyPreview.json from graphify-out/graph.json, but the current SPA does not import or render that preview artifact.

For Docker/TrueNAS installation, including Python/Graphify image setup, bind-mount permissions, Ollama connectivity, and the optional SPA preview sync, see docs/truenas-docker.md.

Install Graphify (Windows)

& "$HOME\.local\bin\uv.exe" tool install "graphifyy[sql]" --python 3.12
& "$HOME\.local\bin\uv.exe" tool update-shell
& "$HOME\.local\bin\graphify.exe" --version

Open a new terminal before calling graphify by name. The SQL extra indexes the SQLite migrations. The repository already has agent instructions; installing the CLI does not require replacing them or installing Git hooks.

Build the graph and wiki

PowerShell note: use graphify . (no leading slash).

# full semantic extraction + wiki (requires backend)
$env:OLLAMA_API_KEY = "local"
$env:OLLAMA_MODEL = "qwen3.5:9b"
& "$HOME\.local\bin\graphify.exe" . --wiki --backend ollama

# local AST-only fallback (no API keys)
& "$HOME\.local\bin\graphify.exe" update .
& "$HOME\.local\bin\graphify.exe" cluster-only . --no-label

Sync UI preview artifacts

After regenerating graphify-out/graph.json, run:

node scripts/graphify_sync_preview.mjs

This writes:

  • web/src/generated/graphifyPreview.json (lightweight graph slice used by the SPA)
  • graphify-out/wiki/index.md (local wiki scaffold)

Toggle in settings

Operators can enable/disable the welcome-screen graph at:

  • Settings -> General -> Show Graphify preview on New chat

The toggle is stored per browser in localStorage (execlaw.chat.graphify_welcome_visible).

Built-in Graphify tool (for local models)

execlaw now exposes a built-in model tool named graphify.

  • Visible at Settings -> Tools as graphify
  • Registered at server boot and synced into tool-access policy
  • Controller-only by default

This means local Ollama-backed models can call Graphify directly during tool-use turns (instead of replying that no graphify tool exists).

Example tool args:

{"action":"build","target_path":".","wiki":true,"backend":"ollama","model":"qwen3.5:9b"}
{"action":"query","question":"How does inbound transport reach TurnExecutor?"}
{"action":"path","from":"crates/server/src/chats.rs","to":"crates/runner-local/src/turn.rs"}

Optional override for executable location:

  • env: EXECLAW_GRAPHIFY_BIN (defaults to graphify)

Built-in Graphiti tool + admin API

execlaw now exposes a built-in model tool named graphiti for temporal-memory search and durable ingestion via a Graphiti-compatible HTTP service.

  • Visible at Settings -> Tools as graphiti
  • Registered at server boot and synced into tool-access policy
  • Default allowed trust class: Controller

Configuration is stored in SQLite and the credential value is stored in the core vault. There is no production environment-variable fallback.

  • PUT /api/admin/graphiti/config accepts base_url, optional api_key_vault_ref, and a write-only optional api_key
  • GET /api/admin/graphiti/config returns the endpoint, credential reference, and whether the referenced secret exists; it never returns the secret value

Admin validation endpoints (auth required):

  • GET /api/admin/graphiti/health
  • POST /api/admin/graphiti/test-call with body { "args": { ...tool args... } }

Example test-call body:

{
   "args": {
      "action": "search",
      "query": "find policy",
      "top_k": 5
   }
}

Obsidian lessons pipeline

The workspace now includes a local .obsidian/ lessons pipeline for persistent, deduped memory notes (Patterns, Mistakes, Decisions, Context) plus a review-only weekly maintenance report.

Scaffold + validation:

pwsh -File scripts/verify_copilot_obsidian_pipeline.ps1 -VaultDir .obsidian

Import lessons from a transcript and regenerate index/report:

pwsh -File scripts/sync_copilot_obsidian.ps1 -VaultDir .obsidian -TranscriptPath <path-to-transcript.jsonl>

One-command maintenance task (repo root):

npm run graph-memory:maintain

That command runs:

  • graphify update . (if Graphify CLI is installed)
  • node scripts/graphify_sync_preview.mjs
  • python scripts/weekly_lessons_maintenance_report.py --vault-dir .obsidian --stale-days 30

Optional auto-start hook (post-commit)

Install a git post-commit hook that runs only when structural files changed. When triggered, it runs the same maintenance script above.

Install it once:

pwsh -File scripts/install_graphify_memory_hook.ps1

Hook runner script:

  • scripts/post_commit_graphify_memory.ps1

Workspace layout

PathPurpose
crates/core/Event log, FSM, migrations (flattened baseline + incremental), SQLCipher-encrypted storage, principal store, memory lifecycle.
crates/session/Per-conversation pipeline composition (text vs voice).
crates/inference-api/OpenAI-compatible LLM client. No cloud SDKs.
crates/model-adapter/Provider-specific prompt + tool-call shape adapters (Qwen, Llama, OpenAI-compatible variants).
crates/runner-local/TurnExecutor — full tool-loop turn path.
crates/runner-protocol/Wire types for the per-conversation runner-container RPC.
crates/runner-binary/Static-musl execlaw-runner binary baked into Dockerfile.runner.
crates/voice-pipeline/STT → LLM → TTS two-lane Tokio graph.
crates/plugin-sdk/plugin.toml manifest parser + ZIP staging.
crates/plugin-host/Plugin registry + lifecycle (install / enable / disable / hydrate / purge).
crates/script/Embedded Rhai engine + primitive bindings (HTTP, sidecar, vault, OAuth, WS, routing, JSON, time).
crates/skills/Skills runtime (capture, retrieve, surface in prompt).
crates/charting/Server-side chart rendering for the chart.render host built-in.
crates/container-manager/bollard client + tiered hardware detection.
crates/policy/Rule of Two, capability tokens, input guards, spotlighting.
crates/vault/OS-keyring master key + Argon2id admin password.
crates/transport-api/Trait a transport plugin implements.
crates/identity-api/Trait an identity-provider plugin implements.
crates/outbox/Outbox relay primitives (idempotency, retry, dead-letter).
crates/server/Axum HTTP + WebSocket surface, sidecar supervisor, admin/webhook routers, chat path, SPA-embed via rust-embed.
crates/mcp-client/MCP server registration + tool dispatch (alternative to plugin tools).
crates/cli/execlaw binary (install, service, doctor, serve, replay, eval, …).
crates/eval-harness/LLM-judge harness against local Qwen.
plugins/In-tree reference + first-party plugins (see Plugins shipped).
web/React + react-bootstrap SPA. Vite + Vitest.
desktop-macos/Tauri 2 menu bar app for Apple Silicon. SMAppService LaunchAgent + WKWebView. Out-of-workspace cargo crate.
desktop-windows/Tauri 2 tray app for x86_64 Windows. NSIS installer + SCM service + WebView2. Out-of-workspace cargo crate.
desktop-linux/Tauri 2 tray app for x86_64 Debian-family Linux. .deb installer + systemd --user unit + webkit2gtk-4.1. Out-of-workspace cargo crate.
scripts/dev-server.{sh,ps1} (cargo-watch wrappers), build-mac.sh / build-windows.ps1 / build-linux.sh (Tauri releases), trace-turn.{sh,ps1} (turn replay).
docs/Architecture + agent-model + plugins + setup walkthroughs + desktop-installations + ollama + screenshots.
evals/Rubric TOML files for the LLM-judge harness.
spec/OpenAPI + AsyncAPI specs.
dist/Built plugin install ZIPs (one per plugin / version).
.github/workflows/CI (per-push), macos-bundle.yml / windows-bundle.yml / linux-bundle.yml (tag-driven .app+.dmg / NSIS .exe / .deb → GitHub Releases).

License

Apache License, Version 2.0 — see LICENSE and NOTICE.

Copyright (c) 2026 Justin Long.

Contributors

nikolacucuk

72 commits

Languages

Rust

61.6%

TypeScript

17.6%

HTML

15.1%

Rhai

3.3%