Skip to content

How RemoteHangar works

A technical overview for the CTO and the security engineer: what runs where, how agents are contained, how tasks flow and how the service updates itself.

Architecture

RemoteHangar is one Node.js service under systemd, with an SQLite database, on a Linux server you own. People use it through a web console that installs as an app on desktop and phone.

The unit of work is a bay: one agent session with its own working directory, its own HOME and its own task queue. A person can own many bays. Every agent turn starts a short-lived agent process inside a sandbox; the process calls the model provider you configure and runs its tools on your server.

Optionally, a small Go connector on another host of yours links it back over mutual TLS. Agents can then run file and shell tools on that host, and the console shows its terminal, logs and health.

RemoteHangar architectureThe browser reaches nginx over HTTPS and WSS. nginx forwards to one RemoteHangar service on your host: REST API, WebSocket, SSE, session runner, queue dispatcher, permissions and a SQLite database. For every turn the service starts a sandboxed bay process, which calls the model provider API itself and reaches trackers and other systems through plugin MCP servers.Developer’s browserSPA / PWA · chat, queue, adminHTTPS · WSSnginx · TLSREMOTEHANGAR — ONE SERVICE ON YOUR HOSTREST API/api/* · /v1/*WebSocketchat stream · terminal · eventsSSEPublic API turn streamingSession runnerstarts and supervises bay processesQueue dispatcherone task per bay at a timePermissionsapproving agent actionsSQLite · WALusers · sessions · task queue · credentials · auditone file on disk, snapshot before every updatea process per turnBay · sandboxown namespaces, read-only root, private HOMEagent processlives for one turnplugin MCP serversstdio; built-in ones run in the serviceAPI · MCPExternal systemsModel provider APIIssue trackersGit hostsYour servers (mTLS)Metrics · tracesNotifications

The sandbox around each agent

Every agent process runs under bubblewrap. The system is mounted read-only apart from the writable exceptions listed below. Privilege escalation is blocked with NoNewPrivileges, so there is no sudo inside a bay.

The service itself runs under a hardened systemd unit: ProtectSystem=strict, ProtectHome=read-only and NoNewPrivileges.

Writes outside /home and the bay’s own directories are blocked. Reads are not yet isolated between bays: an agent can read files the service user can read, and /home is writable from every bay. Treat one installation as one trust group, typically one team. Read isolation is planned.

  • Writable: the bay’s working directory and HOME, the whole /home directory (shared by every bay), the attachment and plugin caches and the agent memory directory
  • Read-only: the rest of the file system, including the release and the data root
  • Private per process: /tmp
  • Optional: Docker inside bays, off by default. Enabling it gives bays the host’s Docker socket, which is root-equivalent on that host.
Bay isolationTwo bays on one Linux server. Each agent process runs in its own namespaces: its working directory, private HOME and the shared caches are writable; the admin-installed catalogue and the rest of the host are mounted read-only; privilege escalation is closed. The network is open on purpose. A sandbox lives for one turn; the session state stays in the database on your disk.ONE LINUX SERVER · THE SERVICE RUNS AS ONE SYSTEM USERbay · aliceagent process in its own namespacesrwbay working directorythe repositories this agent editsrwprivate HOMEagent config, local packagesrwshared cachesattachments and plugin cacheroskills · plugins · agentsthe shared catalogue the admin installsrothe rest of the hostmounted read-only, not hidden; /home stays writable✕sudo and privilege escalationclosed by the kernel (no_new_privs)bay · bobthe same layout, a separate processrwbay working directoryits own repositories and branchesrwprivate HOMEits own config and packagesrwshared cachesthe same caches as every bayroskills · plugins · agentsthe same catalogue, read-onlyrothe rest of the hostother bays included · read isolation: planned✕sudo and privilege escalationclosed for every bayThe network is not isolated, on purposeThe agent needs the model provider API, your MCP servers and git remotes.One sandbox per turnmessageprocess startsagent answersprocess exitssession state stays in the database on your disk

The task queue

Each bay has a queue and works through it one task at a time. A task is either a typed prompt or a ticket from YouTrack, Jira or Notion. People add tasks; RemoteHangar does not poll trackers on its own.

A task stays running across as many chat turns as the work needs. It finishes when the agent marks it done or blocked, or when a person presses Done. A blocked task pauses the queue until someone looks at it. Stopping a running turn puts the task back in the queue.

Queue turns always run as the bay’s owner, never as whoever added the task. Limits keep a queue sane: 10 new tasks per minute per user and 200 unfinished tasks per bay.

  • queued: waiting for its turn
  • running: the agent is working on it
  • done: the agent or a person marked it complete
  • blocked: the agent needs a person; the queue pauses
  • error: the task or the bay failed; the queue pauses
  • cancelled or skipped: a person moved past the task
Queue task lifecycleA person adds a task and it waits as queued. The dispatcher starts it: running, at most one per bay. The agent marks it done or blocked, or a person presses Done; a failure that exhausts three attempts ends in error. Stop or a retryable failure returns the task to queued; the owner’s reply in the chat resumes a blocked task; Retry sends a blocked or failed task back to queued; Skip and Cancel move past it.enqueuedispatcherStop · retryable failureagent marks done · or the Done buttonagent: cannot finishowner replies in the chat3 attempts spentSkip · CancelRetry → back to queuedqueuedwaiting · editablerunningat most one per baydonesummary and artifacts on the cardblockedagent can’t finish · queue pauseserrorattempts spent · queue pausescancelledcancelled by a personskippedskipped by a person1 · A task never closes by itself: the agent marks it done or blocked, or a person presses Done.2 · Queue turns always run as the bay owner.

Where your data lives

Everything is on your disk, in four standard locations. Workspaces live in the directories you allow at install time, /opt/repos and /home by default.

PathWhat it holds
/opt/remote-hangarInstalled releases, one directory per version, and a current symlink
/var/lib/remote-hangarThe SQLite database, skills and plugins, application logs
/etc/remote-hangarconfig.env, install settings and the master key for credential encryption
/var/cache/remote-hangarCaches for attachments and plugins

Public API

Pipelines and internal tools call agents through Public API v1, described in OpenAPI with Swagger UI on your server. Clients authenticate with bearer tokens that carry scopes and expire after 90 days by default; the server stores only their SHA-256 hashes.

Responses stream back as Server-Sent Events. A one-shot request can require output that matches a JSON schema: in strict mode RemoteHangar validates the answer and retries up to three times. Requests can carry an idempotency key, set effort and a token budget, and attach files by URL. Limits apply per API token and per host, and turns end on three kinds of timeout: total time, time to the first event and silence.

Sessions are long-lived bays you can prompt, interrupt and download files from. A one-shot request creates a throwaway bay, runs one prompt and removes the bay afterwards:

curl -N https://hangar.example.com/v1/oneshot \
  -H "Authorization: Bearer $HANGAR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Summarize why the tests in this log fail",
    "idempotency_key": "ci-run-4812",
    "effort": "low",
    "response_format": {
      "type": "json_schema",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": { "summary": { "type": "string" } },
        "required": ["summary"]
      }
    }
  }'

Updates and rollback

A systemd timer runs the updater. It installs a new release only inside your maintenance window and only after checking twice that no agent is busy. You can pin a version, choose the release channel or turn automatic updates off.

Each release manifest is signed with minisign; the signing key never sits on the distribution server. If the host cannot run the verifier, the installer only warns; --require-signature makes the check mandatory. Before an update the database is snapshotted. If the new version fails its health check, both code and database schema roll back automatically.

If a published release turns out to be bad, we move the channel pointer back to the previous version, so servers that have not updated yet never take it.

Requirements

RemoteHangar targets Ubuntu 22.04 on x86_64. The installer needs root; the service runs as its own unprivileged user.

  • OS: Ubuntu 22.04 with systemd. Ubuntu 24.04 blocks the unprivileged user namespaces the sandbox needs.
  • CPU: x86_64
  • Memory: about 350 MB per concurrent agent turn, on top of the base service. The default 3 GB limit fits four concurrent turns.
  • Disk: about 250 MB to download per release and more once unpacked (earlier versions are kept for rollback), plus several hundred MB for optional components (code search, browsers for UI tests), plus your repositories
  • Network: outbound HTTPS to your model provider, directly or through your corporate proxy
  • Optional: nginx with a Let’s Encrypt certificate, set up by the installer

Want to see it on your hardware?

In a pilot we install RemoteHangar with you and connect your tracker and git host.

Discuss a pilot