Exec host refactor plan

Exec host refactor plan

Scope

Use this page when:

  • Designing exec host routing or exec approvals
  • Implementing node runner + UI IPC
  • Adding exec host security modes and slash commands

Goals

  • Add exec.host + exec.security to route execution across sandbox, gateway, and node.
  • Keep defaults safe: no cross-host execution unless explicitly enabled.
  • Split execution into a headless runner service with optional UI (macOS app) via local IPC.
  • Provide per-agent policy, allowlist, ask mode, and node binding.
  • Support ask modes that work with or without allowlists.
  • Cross-platform: Unix socket + token auth (macOS/Linux/Windows parity).

Non-goals

  • No legacy allowlist migration or legacy schema support.
  • No PTY/streaming for node exec (aggregated output only).
  • No new network layer beyond the existing Bridge + Gateway.

Decisions (locked)

  • Config keys: exec.host + exec.security (per-agent override allowed).
  • Elevation: keep /elevated as an alias for gateway full access.
  • Ask default: on-miss.
  • Approvals store: ~/.moltbot/exec-approvals.json (JSON, no legacy migration).
  • Runner: headless system service; UI app hosts a Unix socket for approvals.
  • Node identity: use existing nodeId.
  • Socket auth: Unix socket + token (cross-platform); split later if needed.
  • Node host state: ~/.moltbot/node.json (node id + pairing token).
  • macOS exec host: run system.run inside the macOS app; node host service forwards requests over local IPC.
  • No XPC helper: stick to Unix socket + token + peer checks.

Key concepts

Host

  • sandbox: Docker exec (current behavior).
  • gateway: exec on gateway host.
  • node: exec on node runner via Bridge (system.run).

Security mode

  • deny: always block.
  • allowlist: allow only matches.
  • full: allow everything (equivalent to elevated).

Ask mode

  • off: never ask.
  • on-miss: ask only when allowlist does not match.
  • always: ask every time.

Ask is independent of allowlist; allowlist can be used with always or on-miss.

Policy resolution (per exec)

  1. Resolve exec.host (tool param β†’ agent override β†’ global default).
  2. Resolve exec.security and exec.ask (same precedence).
  3. If host is sandbox, proceed with local sandbox exec.
  4. If host is gateway or node, apply security + ask policy on that host.

Default safety

  • Default exec.host = sandbox.
  • Default exec.security = deny for gateway and node.
  • Default exec.ask = on-miss (only relevant if security allows).
  • If no node binding is set, agent may target any node, but only if policy allows it.

Config surface

Tool parameters

  • exec.host (optional): sandbox | gateway | node.
  • exec.security (optional): deny | allowlist | full.
  • exec.ask (optional): off | on-miss | always.
  • exec.node (optional): node id/name to use when host=node.

Config keys (global)

  • tools.exec.host
  • tools.exec.security
  • tools.exec.ask
  • tools.exec.node (default node binding)

Config keys (per agent)

  • agents.list[].tools.exec.host
  • agents.list[].tools.exec.security
  • agents.list[].tools.exec.ask
  • agents.list[].tools.exec.node

Alias

  • /elevated on = set tools.exec.host=gateway, tools.exec.security=full for the agent session.
  • /elevated off = restore previous exec settings for the agent session.

Approvals store (JSON)

Path: ~/.moltbot/exec-approvals.json

Purpose:

  • Local policy + allowlists for the execution host (gateway or node runner).
  • Ask fallback when no UI is available.
  • IPC credentials for UI clients.

Proposed schema (v1):

{
  "version": 1,
  "socket": {
    "path": "~/.moltbot/exec-approvals.sock",
    "token": "base64-opaque-token"
  },
  "defaults": {
    "security": "deny",
    "ask": "on-miss",
    "askFallback": "deny"
  },
  "agents": {
    "agent-id-1": {
      "security": "allowlist",
      "ask": "on-miss",
      "allowlist": [
        {
          "pattern": "~/Projects/**/bin/rg",
          "lastUsedAt": 0,
          "lastUsedCommand": "rg -n TODO",
          "lastResolvedPath": "/Users/user/Projects/.../bin/rg"
        }
      ]
    }
  }
}

Notes:

  • No legacy allowlist formats.
  • askFallback applies only when ask is required and no UI is reachable.
  • File permissions: 0600.

Runner service (headless)

Role

  • Enforce exec.security + exec.ask locally.
  • Execute system commands and return output.
  • Emit Bridge events for exec lifecycle (optional but recommended).

Service lifecycle

  • Launchd/daemon on macOS; system service on Linux/Windows.
  • Approvals JSON is local to the execution host.
  • UI hosts a local Unix socket; runners connect on demand.

UI integration (macOS app)

IPC

  • Unix socket at ~/.moltbot/exec-approvals.sock (0600).
  • Token stored in exec-approvals.json (0600).
  • Peer checks: same-UID only.
  • Challenge/response: nonce + HMAC(token, request-hash) to prevent replay.
  • Short TTL (e.g., 10s) + max payload + rate limit.

Ask flow (macOS app exec host)

  1. Node service receives system.run from gateway.
  2. Node service connects to the local socket and sends the prompt/exec request.
  3. App validates peer + token + HMAC + TTL, then shows dialog if needed.
  4. App executes the command in UI context and returns output.
  5. Node service returns output to gateway.

If UI missing:

  • Apply askFallback (deny|allowlist|full).

Diagram (SCI)

Agent -> Gateway -> Bridge -> Node Service (TS)
                         |  IPC (UDS + token + HMAC + TTL)
                         v
                     Mac App (UI + TCC + system.run)

Node identity + binding

  • Use existing nodeId from Bridge pairing.
  • Binding model:
    • tools.exec.node restricts the agent to a specific node.
    • If unset, agent can pick any node (policy still enforces defaults).
  • Node selection resolution:
    • nodeId exact match
    • displayName (normalized)
    • remoteIp
    • nodeId prefix (>= 6 chars)

Eventing

Who sees events

  • System events are per session and shown to the agent on the next prompt.
  • Stored in the gateway in-memory queue (enqueueSystemEvent).

Event text

  • Exec started (node=<id>, id=<runId>)
  • Exec finished (node=<id>, id=<runId>, code=<code>) + optional output tail
  • Exec denied (node=<id>, id=<runId>, <reason>)

Transport

Option A (recommended):

  • Runner sends Bridge event frames exec.started / exec.finished.
  • Gateway handleBridgeEvent maps these into enqueueSystemEvent.

Option B:

  • Gateway exec tool handles lifecycle directly (synchronous only).

Exec flows

Sandbox host

  • Existing exec behavior (Docker or host when unsandboxed).
  • PTY supported in non-sandbox mode only.

Gateway host

  • Gateway process executes on its own machine.
  • Enforces local exec-approvals.json (security/ask/allowlist).

Node host

  • Gateway calls node.invoke with system.run.
  • Runner enforces local approvals.
  • Runner returns aggregated stdout/stderr.
  • Optional Bridge events for start/finish/deny.

Output caps

  • Cap combined stdout+stderr at 200k; keep tail 20k for events.
  • Truncate with a clear suffix (e.g., "… (truncated)").

Slash commands

  • /exec host=<sandbox|gateway|node> security=<deny|allowlist|full> ask=<off|on-miss|always> node=<id>
  • Per-agent, per-session overrides; non-persistent unless saved via config.
  • /elevated on|off|ask|full remains a shortcut for host=gateway security=full (with full skipping approvals).

Cross-platform story

  • The runner service is the portable execution target.
  • UI is optional; if missing, askFallback applies.
  • Windows/Linux support the same approvals JSON + socket protocol.

Implementation phases

Phase 1: config + exec routing

  • Add config schema for exec.host, exec.security, exec.ask, exec.node.
  • Update tool plumbing to respect exec.host.
  • Add /exec slash command and keep /elevated alias.

Phase 2: approvals store + gateway enforcement

  • Implement exec-approvals.json reader/writer.
  • Enforce allowlist + ask modes for gateway host.
  • Add output caps.

Phase 3: node runner enforcement

  • Update node runner to enforce allowlist + ask.
  • Add Unix socket prompt bridge to macOS app UI.
  • Wire askFallback.

Phase 4: events

  • Add node β†’ gateway Bridge events for exec lifecycle.
  • Map to enqueueSystemEvent for agent prompts.

Phase 5: UI polish

  • Mac app: allowlist editor, per-agent switcher, ask policy UI.
  • Node binding controls (optional).

Testing plan

  • Unit tests: allowlist matching (glob + case-insensitive).
  • Unit tests: policy resolution precedence (tool param β†’ agent override β†’ global).
  • Integration tests: node runner deny/allow/ask flows.
  • Bridge event tests: node event β†’ system event routing.

Open risks

  • UI unavailability: ensure askFallback is respected.
  • Long-running commands: rely on timeout + output caps.
  • Multi-node ambiguity: error unless node binding or explicit node param.

Related docs