For engineers

One binary. One folder of markdown.

HQ is one Rust binary around one folder of markdown. This page walks through each part in the order you would meet it, with the code paths to read. Everything here comes from the repository documentation. Where a document and the code could drift, the page points at the file.

01

Process model and the single binary

The hq binary is built from the hq-cli crate. A release build is about 58 MB with LTO and symbol stripping. The same file runs the terminal chat, the daemon, the Discord and Telegram relays, the web server and the MCP server. You pick the role with a subcommand.

hq start all starts the long-lived components and hq ps lists the managed processes. An MCP client does not need any of that: it launches hq mcp-serve over stdio itself. hq chat runs the agent in process, so the terminal works with nothing else running.

Configuration lives in ~/.hq/config.yaml, or the file named by HQ_CONFIG_PATH. Any key can also come from an HQ_ environment variable, with __ for nesting. The file and its backups are written owner-only.

Full diagram and text description
Process model: one binary, several roles The hq binary at the top, five roles below it, and the vault underneath that every role reads and writes. hqone Rust binary, about 58 MB Terminal chathq chat Daemonhq start daemon Chat relaysDiscord, Telegram Web serverREST, /ws, /mcp,the PWA MCP serverhq mcp-serve(stdio) The vaultmarkdown files in .vault/ and one SQLite file, .vault/_data/vault.db
Every role is a subcommand of the same binary. hq start runs the long-lived components, while an MCP client launches hq mcp-serve itself.
Text description of this diagram
  • One binary, hq, about 58 MB, built from the hq-cli crate.
  • Five roles come out of it: terminal chat (hq chat), the daemon, the chat relays for Discord and Telegram, the web server that carries REST, the chat socket, the MCP endpoint and the PWA, and the MCP stdio server (hq mcp-serve).
  • All five read and write the same vault: markdown files plus one SQLite database.

02

Crate map

The workspace is Rust edition 2024 with 24 crates and about 289,000 lines of Rust. Foundations hold types, storage and the LLM clients. The agent crate holds the session loop, sub-agents, governance and the context engine. Services wrap those for a transport, and hq-backup writes and restores encrypted backups of the vault and its database. hq-host and hq-sandbox run and confine coding agents, and hq-gate is the signed TCP transport for the host gate over a private tunnel.

Four crates connect devices. hq-bridge pairs machines with a one-time QR code and reconnects them by key. hq-edge is a blind public forwarder that routes by TLS server name to a registered device. hq-net sets up a private WireGuard network, and hq-tunnel is a userspace WireGuard peer for a phone. hq-cli ties them into the binary, and hq-desktop holds the macOS desktop app's logic with no GUI dependency.

The stack, in short: Tokio for async, Axum and tower-http for the web server, rusqlite (bundled, WAL mode) for storage, rmcp for MCP, serenity for Discord, teloxide for Telegram, clap for the CLI and figment for configuration. LLM calls go through async-openai with a swappable base URL, which is how OpenRouter, Ollama and other OpenAI-compatible backends plug in. A direct Anthropic Messages client sits beside it, with prompt caching. Every call is priced in one place, and spend shows on the /usage page.

Full diagram and text description
Crate map Twenty-four crates in six bands: foundations, intelligence, services, the agent runtime, the network and the apps. Foundations hq-coretypes, config, errors hq-vaultnotes, tasks, frontmatter hq-dbSQLite, FTS5, embeddings hq-llmproviders, backend chains Intelligence hq-agentsession loop hq-memoryconsolidate, forget hq-toolsabout 100 tools hq-convertdocuments, OCR hq-exportPDF, DOCX, more Services hq-mcp2-tool gateway hq-daemonscheduler hq-relayDiscord, Telegram hq-webREST, WS, PWA hq-updatesigned updater hq-backupencrypted backup Agent runtime hq-hostpty panes, control socket hq-sandboxsandbox-exec and bwrap policy hq-gatesigned link to a remote host Network hq-bridgeQR pairing hq-edgepublic forwarder hq-netWireGuard network hq-tunnela phone as a peer Apps hq-clibuilds the hq binary: clap commands, start, install, doctor, update hq-desktoplogic for the desktop app
The 24 crates of the Cargo workspace, grouped by role. This is a map of responsibilities, not a dependency graph.
Text description of this diagram
  • Foundations: hq-core (types, config, errors, token counters), hq-vault (notes, tasks, frontmatter), hq-db (SQLite, FTS5, embeddings) and hq-llm (providers, backend chains, the direct Anthropic client).
  • Intelligence: hq-agent (session loop, sub-agents, governance, context engine), hq-memory (consolidator, forgetter), hq-tools (about 100 tools), hq-convert (document conversion) and hq-export (native note export).
  • Services: hq-mcp (the 2-tool gateway), hq-daemon, hq-relay (Discord and Telegram), hq-web (REST, WebSocket, PWA), hq-update (the signed updater) and hq-backup (encrypted backup and restore).
  • Agent runtime: hq-host (the built-in host for long-lived coding agents), hq-sandbox (the process sandbox policy for one agent) and hq-gate (the signed transport to a host on another machine).
  • Network: hq-bridge (pairing machines with a QR code), hq-edge (a self-hosted public forwarder), hq-net (the WireGuard private network) and hq-tunnel (a userspace WireGuard peer for a phone).
  • Apps: hq-cli, which builds the hq binary, and hq-desktop, the logic behind the macOS desktop app (host lifecycle, pairing, Connect Claude).

03

The vault and the memory layers

The vault is a folder, .vault/, and it is the single source of truth. Notes, memory, skills and conversation threads are markdown with YAML frontmatter. One SQLite file, .vault/_data/vault.db in WAL mode, holds the indexes: FTS5 full-text search, embedding vectors, link graphs and the registries the daemon and relays need. Delete the database and the notes are still there.

Embeddings come from OpenRouter when a key is set, and from Ollama (nomic-embed-text by default) when it is not. Memory, consolidation and skill review call the router under the aliases dream, bulk and ingest. Point those at a local model with a backends: block and they stay on your machine.

Memory

The hq-memory crate has an ingester, a consolidator, a querier, a forgetter and an entity graph. The daemon runs consolidation, embedding, note tagging and forgetting on its memory tier, so memory is something the vault does for itself over time, not a log that grows forever.

Context frames

Each turn, the context engine builds a token-budgeted frame from five layers: system, the user message, memory, thread and injections. Surplus tokens cascade between layers, with the thread getting 50 percent, injections 35 percent and memory 15 percent. Private tags are stripped from the memory layer, and older thread messages are compacted.

Who may see what

Notes and tasks can carry a label: private to named people, shared, public, or unmarked, which means owner only. A shared channel discloses only what every verified participant may see, and an unresolved identity is denied by default. The design note lists what is not covered yet.

Full diagram and text description
The vault and the context engine The vault directories on the left feed a five-layer context frame on the right. The daemon memory tier writes back to the vault. Vault (.vault/) _system/SOUL, MEMORY, CRON-SCHEDULE _threads/conversation history Notebooks/notes, projects, knowledge skills/markdown skills _data/vault.dbSQLite, FTS5 search, embeddings Context engine, five layers 1 SystemSOUL and harness instructions 2 UserMessagethe current turn 3 Memorylong-term facts 4 Threadrecent messages, older ones compacted 5 Injectionspinned notes and search results Daemon memory tier Consolidation Embeddings and tagging Forgetting read writes back
The vault is the center. The context engine reads it into token-budgeted frames, and the daemon memory tier keeps it tidy.
Text description of this diagram
  • Vault: _system/ (SOUL, MEMORY, CRON-SCHEDULE and other system markdown), _threads/ (conversation history), Notebooks/ (notes, projects, knowledge), skills/ and _data/vault.db (SQLite in WAL mode, with FTS5 and embeddings).
  • Context engine: five layers per frame. 1 System (SOUL plus harness instructions), 2 UserMessage (the current turn), 3 Memory (long-term facts), 4 Thread (recent messages, older ones compacted), 5 Injections (pinned notes and search results).
  • Surplus tokens cascade between layers: thread 50 percent, injections 35 percent, memory 15 percent.
  • The daemon memory tier runs consolidation, embeddings and note tagging, and forgetting, and writes the results back to the vault.

04

The daemon and its scheduled tasks

The daemon is a plain interval scheduler. It ticks every 5 seconds and runs whichever tasks are due, one after another, each with a timeout derived from its interval tier. A missed task is detected on the next start. After every tick it writes DAEMON-STATUS.md at the vault root, so you can read what ran with cat.

The fast tier handles approvals, harness-session supervision, the heartbeat, value-bus delivery and the email poll. The memory tier handles consolidation, embeddings, note tagging and forgetting. The maintenance tier handles vault health, thread rotation, clean-up, a disk watchdog and SQLite vacuum. Beside the scheduler, an agent worker triages inbound events and email, and a machine profile records which CLIs the host has.

Recurring prompts are not daemon tasks. They are /watch rows, covered with the relays below.

Full diagram and text description
The daemon scheduler A five-second tick fans out to three tiers of tasks. Two loops run beside the scheduler, and a status file is written after every tick. Scheduler tickevery 5 seconds Fast tierapprovalsharness-session supervisionheartbeatvalue-bus deliveryemail poll Memory tierconsolidationembeddingsnote taggingforgetting Maintenance tiervault health, thread rotationclean-up, disk watchdogSQLite vacuum DAEMON-STATUS.mdwritten after every tick Runs beside the scheduler Agent workertriages events and email Machine profilerecords host CLIs
One tick every 5 seconds, three tiers of interval tasks, two side loops and a status file you can read.
Text description of this diagram
  • A scheduler tick fires every 5 seconds.
  • Fast tier: approvals, harness-session supervision, heartbeat, value-bus delivery and email poll.
  • Memory tier: consolidation, embeddings, note tagging and forgetting.
  • Maintenance tier: vault health, thread rotation, clean-up, the disk watchdog and SQLite vacuum.
  • Beside the scheduler run the agent worker, which triages inbound events and email, and the machine profile, which records the host CLIs in MACHINE.md.
  • DAEMON-STATUS.md at the vault root is written after every tick.

05

Chat relays

Discord and Telegram are built into hq-relay as feature-gated bridges behind one unified bot. They share commands and watch firing, so behavior matches. Send !cancel in either and the agent stops at the next tool boundary and replies with a summary of turns and files touched. A heartbeat message shows the turn count and the tool currently running.

Chat bridges are gated to an owner. Run hq pair, then send /pair <code> in a private Telegram chat or !pair <code> in a Discord DM. Earlier versions let the first sender become the owner. That was changed because it hands the agent to whoever messages first.

Turns that outlive the chat

A turn has an ack window, 270 seconds by default. If the turn runs longer, the chat gets an immediate acknowledgement and the session detaches into a persisted background_turns table, then delivers its result when it finishes. A reconciler runs at daemon start and every 6 hours. It marks rows that were running when something died as interrupted, offers a resume <id>, and retires rows older than five days.

/watch <minutes> [for <N>h] <prompt> stores a durable recurring turn in the same table, so watches survive restarts. A reply containing WATCH_DONE delivers once and stops the watch.

Session limits

Sessions have no turn-count ceiling. They end on completion, cancellation, an optional USD budget cap, or a wall-clock limit that defaults to five hours.

Full diagram and text description
Channels converge on one agent session Discord and Telegram go through the relay, the PWA through the web server, and the terminal runs in process. All reach one agent session and one vault. Discord Telegram Web PWA Terminalhq chat Relayunified bot, !cancel,background turns, /watch Web serverWebSocket and REST Agent sessionone loop, one memory,one thread history Vaultthreads and memory in process, no server needed
Four ways in, one agent loop, one vault. That is why a thread started on your phone continues in the terminal.
Text description of this diagram
  • Discord and Telegram connect to the hq-relay crate, the unified bot, which handles cancel, background turns and watches.
  • The web PWA connects to hq-web over a WebSocket and REST.
  • The terminal chat (hq chat) runs the agent in process.
  • All four paths reach one agent session in hq-agent, which reads and writes one vault holding the shared thread history and memory.

06

The web PWA

The Control Center is a React 19 app on TanStack Start and Vite, built to a static single-page bundle. hq start all serves it from :5678 along with the REST API and the chat socket, from web/dist next to the vault or the directory in web_static_dir. There is no separate web server process.

It streams the agent live, browses and searches the vault, shows daemon status, runs the tasks views, lists and drives sessions, renders DOCX, XLSX and PDF, and takes push notifications. It installs on a phone like any PWA.

Auth in the browser

The server binds to 127.0.0.1 by default. With web_auth_token set, the token is accepted only as a Bearer header, never in a URL. A browser cannot set headers on a WebSocket, so the app asks POST /api/ws-ticket for a ticket that works once, lasts 30 seconds and opens only /ws. An origin guard refuses requests from origins that are not loopback, not the server's own and not listed in web_allowed_origins, which also stops DNS rebinding. The response CSP has no unsafe-eval and no script unsafe-inline, and remote images are blocked on purpose, because an image URL is a way to send data out of a rendered note. Details: web and MCP auth.

07

The MCP server and the two-tool gateway

HQ is an MCP server so the agents you already use can reach your vault, tasks and sessions. A client always sees exactly two tools. hq_discover searches the registry by keyword and returns matching tools with their descriptions. hq_call runs a tool by name with JSON input. The registry behind them holds about a hundred tools, and a client that calls hq_discover at runtime always sees the live list.

hq mcp install --target claude-code writes the client config for Claude Code, Claude Desktop, Cursor, VS Code, Copilot, OpenCode, Antigravity or a project file, and hq mcp doctor checks the connection.

Keys and scopes

Over HTTP, /mcp refuses every call unless a key is configured. There are three scopes. A full key sees everything. A read-only key sees an allowlist. A handoff key gets that read-only set, minus session logs, plus filing tasks, starting sessions and asking questions in read-only mode, and nothing else. The gateway sets the scope itself, so a client cannot claim a wider one. Treat the handoff key as the power to start a coding agent anywhere you allow it, and set agent_host.handoff_cwd_allow to bound where.

Both directions

HQ also bridges remote MCP servers. Each remote_mcp: entry becomes a <name>_discover and <name>_call pair in the registry, kept out of unattended sessions by default. From outside, hq_ask lets an MCP client put a question to HQ's own chat agent, in an ordinary web thread you can open and continue (docs).

Full diagram and text description
The MCP server and its two-tool gateway MCP clients reach a gateway with two tools, which fronts a registry of about a hundred tools. Remote MCP servers are bridged in. MCP clientsClaude Code, Cursor,VS Code, Copilot,OpenCode, others Transportstdio: mcp-serveHTTP: /mcpAPI key required 2-tool gatewayhq_discoverhq_call Tool registryabout 100 toolsvault, tasks,sessions, web... Remote MCP serversbridged in as <name>_discover and <name>_call tools
Clients see two tools no matter how many HQ has. hq_discover finds, hq_call runs.
Text description of this diagram
  • MCP clients such as Claude Code, Cursor, VS Code, Copilot and OpenCode connect over stdio (hq mcp-serve) or over HTTP at /mcp. HTTP needs an API key and refuses requests without one.
  • The gateway exposes exactly two tools: hq_discover and hq_call.
  • Behind it sits the tool registry of about 100 tools: vault, tasks, harness sessions, sub-agents, web and more.
  • Remote MCP servers listed under remote_mcp in the config are bridged into the registry as name_discover and name_call.

08

The built-in host and supervised coding-agent sessions

HQ's built-in host (hq host) owns the terminals. It recognises which coding agent sits in a pane and reports its state: idle, working, blocked on a dialog, or done. HQ drives it over a local socket. Claude Code, Codex, Cursor, Pi, OpenCode, Copilot CLI, Kimi, Qwen and Antigravity are supported harnesses, and you can define your own profiles. This replaced an earlier tmux runner that could only guess state from screen text.

Personal agents

Hermes Agent and OpenClaw run in the same host as a second kind of harness, general-purpose agents. Every session has a purpose (research, review, monitor or general), and a request to give one code work is refused before anything starts. They get no HQ token and no MCP wiring: HQ types into them and reads their screen as untrusted text. Named in a stack: block, each pulls the tasks assigned to it and runs one as a single job, and the process ending puts the task into review. Setup and removal are in the personal agents guide.

Hosts

A host is a machine running hq host. native is the machine HQ runs on. Any other host is a machine you paired with hq host join and is reached over ssh with a dedicated key that is pinned to one source address and to a forced command, hq host gate, which accepts one request as JSON on stdin and refuses every method outside an allowlist. Agents run in a process sandbox. The default profile, open, walls off only HQ itself: the host's tokens, ~/.hq, other processes' environments, other agents' logins and, unless you turn it off, your credential stores. strict adds an egress allowlist and read and write allowlists. That is how HQ on a VPS can run and watch agents on a laptop, whenever the laptop is on and reachable. Connections are reused between calls, and a write is never retried because the gate may already have run it.

Spawning and watching

harness_session_spawn starts an agent and returns a session id. Send, wait, logs, status, stop and resume sit beside it, and hq sessions mirrors them in the CLI. A supervisor in the daemon polls every host once a minute. It alerts once when a session blocks and once when an agent finishes a turn. A host that cannot be reached counts as unknown, never as exited: a sleeping laptop leaves its sessions running and they are watched again when it answers.

HQ does not answer dialogs for you. Claude Code's workspace-trust prompt defaults to "No, exit", so pressing Enter would quit it. A blocked launch is reported with the screen, and you answer with explicit keys.

Sessions that work on a task

A session can be linked to a task. Each event leaves a task comment, and the task moves from in progress to ready for review or blocked as the session finishes or exits. By default HQ never marks a task complete, because an agent saying it is done is not verification. With task verification turned on (it is off by default), HQ completes a task only from evidence the agent could not have written, such as a merged pull request with green checks, and measures how often it is wrong. Comments never quote the pane, since pane text is untrusted input.

Handoff

harness_session_handoff lets an outside client, such as another agent over MCP, start tracked work in one call. It files or reuses a task by an idempotency key, starts one linked session with the acceptance criteria as its definition of done, and opens a chat thread that owns the session. It is idempotent: the same key returns the same task, and a task that already has a running session gets no second one.

Watching and driving from a web chat

A chat can watch a session. Watching posts short updates and never quotes the screen. Driving lets HQ act as the session's project manager: read the session, send the next instruction, approve a permission prompt or resume an exit, toward the linked task's goal. It asks you instead when a step is destructive, touches credentials, deploys or spends money. A new watch starts with Drive on only when the task gives it a usable goal, and observation-only otherwise. A turn that has read untrusted content starts new watches with Drive off, and a tool can never turn it back on once you switched it off. Mechanical limits cut it off without relying on the model: an instruction budget per session, a cap on key presses, a no-progress guard, and a cap on sessions driven at once. Each one switches Drive off and says why. The Sessions page shows every session with its task, host, goal and live state.

Full reference, including setup and troubleshooting: docs/AGENT_SESSIONS.md.

Flat isometric illustration: a tall building with watchful eyes overlooking rows of small cube robots typing at desks, one raising a hand.

codex is waiting on a trust prompt. HQ tells you and never answers it for you.

  • claude-coderunning
  • codexblocked
  • opencodedone
Session state Idle Working Blocked Done
Full diagram and text description
Supervised coding-agent sessions on the built-in host HQ drives the built-in host directly on this machine and on a paired machine through a restricted ssh gate. The supervisor polls every host once a minute. HQ harness_session toolsspawn, send, wait, logs Supervisorpolls every host each minute Session registryone row per session Reportstask comments, web chat,relay alerts when anagent blocks or finishes Host: local The hosthq host Agents in panesClaude Code, Codex, OpenCode,Cursor, Copilot CLI, Kimi... Host: remote, for example a laptop ssh gatededicated key,forced command The host Agentsin panes Host asleep or off the tailnetsessions stay running, watching resumes local ssh
A host is any machine running hq host. HQ treats an unreachable host as unknown, never as exited.
Text description of this diagram
  • On the HQ side: the harness_session tools start and steer sessions, the supervisor polls each host once a minute, the registry holds one row per session, and task comments and chat updates report what happens.
  • Local host: HQ calls the host over its local socket. The host owns the panes where the coding agents run, for example Claude Code, Codex, OpenCode, Cursor, Copilot CLI, Kimi and Qwen.
  • Remote host, for example a laptop: HQ connects over ssh with a dedicated key that is restricted to one forced command, the gate. The gate passes only the host API. The host then runs the agents in a sandbox.
  • If a remote host sleeps or leaves the tailnet, its sessions stay running and watching resumes when it answers.

09

Tasks

Tasks are native, with no external tracker to mirror. The model is spaces, then initiatives, then tasks, then comments, with sub-tasks, start dates and soft dependencies. One shared write surface in hq-db backs the MCP tools (task_list, task_create, task_update, task_comment_add and friends), the REST API with its WebSocket broadcast, the web UI with list, board and timeline views, and hq task in the CLI.

Tagging a task routes it to an agent mailbox synchronously. A person's tasks live in a private space that only that person's turns can read. See the design note, and docs/TASK_VERIFICATION.md for the opt-in verification.

10

Governance and the bash sandbox

The agent's bash tool runs model-written shell commands, so HQ does not rely on the model refusing. A governance layer denies dangerous effects mechanically, before a tool runs, and it applies in permissive modes too.

Credential files such as ~/.ssh, cloud credentials and the HQ config are unreachable always. A session becomes tainted once a tool returns untrusted content: web fetch and search, mail, converted documents, other agents' mailboxes, session logs, pane reads, vault reads and any remote MCP result. Taint is shared with every sub-agent the session spawns, so delegating cannot launder it. After taint, project secrets such as .env files and outbound network from bash (curl, ssh, nc and similar) are denied.

For bash, three layers follow. Text checks catch honest mistakes and cheap tricks, but about 100 known obfuscation shapes are pinned in tests and some still get through, so they are speed bumps. An environment allowlist starts the child with an empty environment and a short list of variables. HQ's own keys are refused even if you list them. Then the OS sandbox: bubblewrap on Linux, sandbox-exec on macOS. On Linux it gives a read-only root, a fresh /proc in a new pid namespace, writable binds only for home, the working directory, temp and the vault, and optionally no network. Config, env files and private SSH keys are masked.

governance.bash.sandbox takes off, best_effort or required. After 0.9.0 the default is required: if no sandbox backend works, every bash command is refused and hq doctor says so.

What this does not cover is on the security page.

Full diagram and text description
What stands between a tool call and its effect Governance rules run before any tool. For the bash tool, three more layers follow: text checks, an environment allowlist and an OS sandbox. Governance, every tool call 1 Tool callchosen by the model,maybe steered byuntrusted content 2 Credential rulesssh, cloud, HQ configrefused, always 3 Taint trackerweb, mail, docs, notesmark the session 4 After taintsecret files and bashnetwork are denied Bash tool only 5 Text checksspeed bumps,not the boundary 6 Env allowlistchild starts with anempty environment 7 OS sandboxbubblewrap on Linux,sandbox-exec on macOS 8 Command runssecret files masked then, for bash
The first row denies dangerous effects before a tool runs. The second row is the boundary for shell commands.
Text description of this diagram
  • Row one, governance, runs for every tool call. 1 The model proposes a tool call, possibly steered by untrusted content. 2 Credential rules refuse SSH keys, cloud credentials and HQ config always. 3 The taint tracker marks a session once it has read untrusted content such as web pages, mail, documents or notes. 4 After taint, secret files and outbound network from bash are denied.
  • Row two applies to the bash tool. 5 Text checks catch honest mistakes and cheap tricks but are not the boundary. 6 The environment allowlist starts the child with an empty environment plus a short list. 7 The OS sandbox is bubblewrap on Linux and sandbox-exec on macOS. 8 The command runs with secret files masked.

11

The signed release and pull-updater pipeline

A server instance pulls its updates. Nothing on the build side holds a credential for any instance, and instances hold only a public key.

Build side

Every merge to main runs tests, builds on an older Ubuntu image so the binary needs only an older glibc, and publishes a signed prerelease named like 0.9.1-main.6. The job that runs repository build code never sees the signing key: a separate sign job does. Each release carries a binary archive per platform (Linux x86_64, Linux ARM, Apple Silicon and, since 0.9.1-main.243, Intel macOS), the web bundle, SHA256SUMS, a manifest.json and its .minisig. Channel pointers (channel-main, channel-stable) are small signed JSON files that say which release a channel currently means. Promoting to stable signs a new pointer for an existing release. Nothing is rebuilt.

Instance side

  1. A systemd timer fires every 10 minutes with a short random delay and runs hq update --apply under an exclusive lock.
  2. It verifies the pointer signature, checks that the manifest URL lives under the configured repository, then verifies the manifest signature and its SHA-256, the schema and the minimum updater version.
  3. Equal or lower versions do nothing. A pointer with a lower sequence number than one already accepted is refused as a replay.
  4. It downloads with hard size caps, verifies each artifact's SHA-256 against the signed manifest, and unpacks with a strict tar reader that rejects links, devices, absolute and parent paths and duplicates.
  5. It snapshots the vault database with VACUUM INTO, as the service user, never as root.
  6. It runs the staged binary as the service user with --version and requires the exact build. This is the only downloaded code that runs before the swap.
  7. It swaps the binary and web files by rename, keeping the last three binaries and one old web tree, restarts the service and polls /health until it reports the new git sha.
  8. On success it runs hq install --upgrade. On failure it rolls back, restores the database only if migrations ran, blocks that version for 24 hours and sends an alert. A crash mid-update is recovered from a journal on the next run.

Signatures are minisign (Ed25519), verified in process. The key is read from a root-owned file or compiled in, and cannot be overridden by an environment variable. Key rotation is a manual step. A compromised signing key and a stale-pointer freeze attack are listed as not mitigated. Read docs/UPDATE_SYSTEM.md for the contract.

  1. 1Timer fires every 10 minutes, under a lock
  2. 2Verify pointer and manifest signatures
  3. 3Refuse equal, lower or replayed versions
  4. 4Download with size caps, check SHA-256
  5. 5Snapshot the vault database
  6. 6Run the staged binary's --version
  7. 7Swap by rename, wait for /health
  8. 8Upgrade, or roll back and alert
Full diagram and text description
Signed release and pull-based updater CI builds and signs a release. An instance pulls it on a timer, verifies every signature and hash, swaps, checks health and rolls back on failure. Build side (CI) Merge to main Test, then buildno secrets in these jobs Signthe only job with the key GitHub releasebinary, web files,manifest + .minisig,signed channel pointer Instance (systemd timer, every 10 minutes) 1 Verifypointer and manifestsignatures, hashes 2 DownloadSHA-256 and strictarchive checks 3 Snapshotdatabase, VACUUM INTO 4 Staged check--version as serviceuser must match 5 Swapbinary and web files,by atomic rename 6 Restartpoll /health for thenew git sha Outcomehealthy: install --upgradefailed: roll back,block that version,send an alert pulls over HTTPS
The build side holds the signing key and no credentials for any instance. Each instance holds only the public key.
Text description of this diagram
  • Build side: a merge to main triggers tests, then a build job with no secrets, then a sign job that is the only place the signing key lives, then a GitHub release with the binary, web files, manifest.json and its minisign signature, plus a signed channel pointer.
  • Instance side, in order: a systemd timer starts the updater every 10 minutes. It verifies the channel pointer signature and the manifest signature and hash. It downloads artifacts and checks SHA-256 and archive safety. It snapshots the database with VACUUM INTO. It runs the staged binary with --version as the service user and checks the build. It swaps the binary and web files by rename. It restarts the service and polls /health for the new git sha.
  • Outcome: on success it runs hq install --upgrade. On failure it rolls back the binary, web files and, if needed, the database, blocks that version for 24 hours and sends an alert.
  • The instance pulls from the release over HTTPS. The build side never connects to the instance.

12

Deployment topologies

Laptop only. Run hq start all. The web server binds to loopback. Set web_auth_token on a shared machine. This is the default and needs no network setup.

VPS plus Tailscale. Provision a Debian or Ubuntu box with deploy/setup-vps.sh, install the signed updater with deploy/install.sh, and keep HQ on loopback. Caddy bridges to tailscale serve, which terminates HTTPS on your tailnet only, so the UI never touches the public internet. List every origin you serve the UI from in web_allowed_origins. HQ on the VPS can then drive the host on a laptop over the gated ssh key.

Docker. The image ghcr.io/calvinmagezi/hq is assembled from the same signed release artifacts as the install script, for linux/amd64 and linux/arm64, and runs as a non-root user with its state under /data. See docs/DOCKER.md.

Windows. Full HQ runs inside WSL2. On a computer that blocks WSL2, HQ Lite runs natively as one program with no administrator rights: the web app, tasks, notes and search, without coding agents, chat bots or the sandbox. See docs/WINDOWS.md and docs/HQ_LITE.md.

An Android phone (developer preview). The mobile profile is Full HQ for a machine with no shell sandbox, no git and no self-updater, plus a data saver. The APK is one you build yourself from apps/android. It is not yet verified that a sleeping, unplugged phone answers in time. See docs/HQ_MOBILE.md and apps/android/README.md.

A device behind NAT, with hq edge. A public forwarder you run yourself on a machine you control. It reads only the TLS server name, never decrypts, and passes the bytes to the registered device over an iroh stream. TLS ends on the device, which holds its own certificate. It is off by default and there is no project-run edge. See docs/EDGE.md.

A private network, with hq net. A WireGuard hub on a Linux server and your own devices as spokes, so the web UI, /mcp and the host gate are reachable only from devices you admitted. Release builds do not include the root applier yet, so that step builds it from source. See docs/PRIVATE_NETWORK.md.

Two shapes publish anything. A plain MCP endpoint goes through a proxy that exposes /mcp and /health and nothing else, with an API key set. The claude.ai connector publishes /mcp, /oauth/token, /oauth/revoke and the /.well-known/oauth-* metadata documents, and the consent page stays private (docs/CLAUDE_CONNECTOR.md, deploy/Caddyfile.connector). /ws and /api must never be public. A host runs one deploy path only: the pull updater, or a manual build, not both. Steps are on the install page and in deploy/README.md.

Full diagram and text description
Two deployment shapes Left: everything on one laptop, bound to loopback. Right: HQ on a VPS, reached over Tailscale only, updating itself from signed releases, optionally driving the host on a laptop. A. Laptop only Browser or terminalhttp://127.0.0.1:5678, hq chat hqweb_bind 127.0.0.1 (default) Vaulton your disk The hostlocal coding agents B. VPS plus Tailscale Tailnet devicephone, laptop browser tailscale serveHTTPS, tailnet only Caddy127.0.0.1:4749, loopback only hq on the VPS127.0.0.1:5678 Updater timersigned releases Laptop hostover gated ssh
Nothing here needs a public port for the web UI. Only the MCP endpoint may be published: /mcp and /health through a proxy, plus the OAuth paths when the claude.ai connector is on.
Text description of this diagram
  • Laptop only: a browser or terminal talks to hq bound to 127.0.0.1 on port 5678. hq reads and writes the vault on disk and drives the built-in host with coding agents.
  • VPS plus Tailscale: a device on your tailnet reaches tailscale serve, which terminates HTTPS on the tailnet. It forwards to Caddy bound to 127.0.0.1:4749, which forwards to hq on 127.0.0.1:5678. The updater timer pulls signed releases. HQ can drive the host on a laptop over a restricted ssh key.

13

Source map

Where to start reading, relative to the repository.