228 lines
13 KiB
Markdown
228 lines
13 KiB
Markdown
# Glove architecture
|
|
|
|
## Scope
|
|
|
|
Glove provides three execution surfaces:
|
|
|
|
| Surface | Purpose | Current boundary |
|
|
| ------------ | ----------------------------------------------------------- | --------------------------- |
|
|
| `glove run` | Contain an agent and mediate MCP tool calls | Public CLI |
|
|
| `glove exec` | Contain a direct agent process | Public CLI |
|
|
| `gloved` | Validate Sage plans, persist sessions, and deliver receipts | Owner-local control service |
|
|
|
|
The public CLI is usable for local containment. The distributed Sage session
|
|
surface is incomplete and must not advertise remote-launch readiness. A
|
|
`remote_linux_container` runtime can be constructed from validated operator
|
|
configuration, but it is deliberately non-operational and advertises no
|
|
lifecycle, runtime-adapter, resource-enforcement, or receipt capability.
|
|
|
|
## Process model
|
|
|
|
```text
|
|
operator or Sage controller
|
|
│
|
|
▼
|
|
Glove control plane ───────► audit and receipt journals
|
|
│
|
|
├── policy engine
|
|
├── MCP extensions ─► upstream tool servers
|
|
│
|
|
▼
|
|
OS sandbox
|
|
└── agent process
|
|
```
|
|
|
|
The control plane remains outside the sandbox. It owns policy evaluation,
|
|
upstream transport, audit output, protected filesystem descriptors, and session
|
|
state. The agent receives only the endpoints and paths required by its selected
|
|
mode.
|
|
|
|
MCP upstreams are separate host processes. Glove filters requests to them, but
|
|
does not sandbox the upstream processes themselves.
|
|
|
|
## Platform isolation
|
|
|
|
| Property | Linux | macOS |
|
|
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
| Process visibility | PID namespace | SBPL process policy |
|
|
| Filesystem | mount namespace, `pivot_root`, read-only binds, private writable mounts | deny-default SBPL path rules |
|
|
| Network | route-less network namespace; offline socket denial or private-loopback access to the authenticated audited proxy through an inherited Unix descriptor channel | deny-default SBPL network rules |
|
|
| Identity | user namespace and UID/GID mapping | invoking user |
|
|
| IPC and hostname | IPC and UTS namespaces | SBPL policy |
|
|
| Resource limits | private cgroup/quota/watchdog implementation | incomplete for the Sage six-limit contract |
|
|
|
|
Linux launches the child through `clone3`, configures the namespace and mount
|
|
perimeter, applies seccomp, then releases the child to execute. Writable
|
|
materializations are quota-backed; retained copies use persistent hard-sized
|
|
ext4 loop images while ephemeral copies use tmpfs. Read-only inputs are
|
|
descriptor-pinned.
|
|
For approved egress, a namespace-local helper passes accepted loopback sockets
|
|
to a host relay with `SCM_RIGHTS`. The relay can connect only to the per-run
|
|
authenticated proxy; it never exposes a host-network socket or DNS service to
|
|
the agent. The namespace-local helper consumes one PID within the configured
|
|
session PID limit.
|
|
macOS constructs a deny-default sandbox profile and applies it before executing
|
|
the child.
|
|
|
|
## Components
|
|
|
|
| Namespace | Responsibility |
|
|
| ------------ | ---------------------------------------------------------------------------------- |
|
|
| `container` | sandbox creation, mounts, limits, output accounting, terminal receipts |
|
|
| `control` | Unix control protocol, authentication, session registry, receipt delivery |
|
|
| `supervisor` | canonical plan validation, local alias resolution, bundle resolution |
|
|
| `mcp` | bounded JSON-RPC framing and upstream clients |
|
|
| `policy` | tool and argument authorization |
|
|
| `kernel` | extension registration and dispatch |
|
|
| `audit` | structured local activity records |
|
|
| `run` | CLI orchestration for `run` and `exec` |
|
|
| `host` | strict XDG configuration, machine setup, diagnostics, and local project enrollment |
|
|
| `reflect` | compile-time extension metadata experiments |
|
|
|
|
Public headers live under `include/glove/`. Implementations mirror that layout in
|
|
`src/`. Tests are separated by concern under `tests/`.
|
|
|
|
## MCP request flow
|
|
|
|
For `glove run`:
|
|
|
|
1. Glove starts the configured upstream servers and completes MCP initialization.
|
|
2. The contained agent sends a bounded JSON-RPC request.
|
|
3. The kernel resolves the extension and tool name.
|
|
4. The policy engine validates tool access and configured argument rules.
|
|
5. The extension forwards an allowed request to the selected upstream.
|
|
6. Glove returns the response and appends the audit event.
|
|
|
|
Malformed frames, unknown tools, policy failures, transport errors, and audit
|
|
append failures fail closed.
|
|
|
|
`glove exec` bypasses the MCP kernel. It is intended for agents that manage
|
|
their own tool protocol, so its security boundary is the OS sandbox and explicit
|
|
filesystem/environment exposure.
|
|
|
|
## Sage session flow
|
|
|
|
`gloved` uses an owner-only Unix socket and a per-start bootstrap secret. Its
|
|
public control methods provide capability discovery, canonical plan validation,
|
|
durable create/status operations, bounded receipt pages, and exact
|
|
acknowledgement.
|
|
|
|
`glove setup` creates owner-only XDG configuration, runtime/state directories,
|
|
key material, and an optional protected-root policy. `glove init` then creates
|
|
a generation-bound exposure through the authenticated local control socket.
|
|
Project files are never configuration input, and raw paths never cross P2P.
|
|
|
|
`glove daemon` installs and controls a fixed per-user service through systemd
|
|
on Linux or launchd on macOS. The CLI resolves the local `gloved` executable,
|
|
binds it to the protected config, writes the service definition atomically, and
|
|
invokes the service manager without a shell. It never accepts service labels,
|
|
extra arguments, environment, or lifecycle requests from P2P.
|
|
The Linux unit enters an unprivileged user namespace and a private mount
|
|
namespace before executing `gloved`; that gives the current user namespace only
|
|
the mount authority needed for quota-backed private session filesystems.
|
|
Systemd separately delegates the `cpu`, `memory`, and `pids` controllers to the
|
|
unit. Neither mechanism grants host-root authority.
|
|
|
|
When Sage configures `glove_activation_mode = "user_service"`, `saged` first
|
|
asks the platform user service manager to start the fixed local Glove unit. It
|
|
waits for `gloved.sock` and `bootstrap-secret` before health and capability
|
|
negotiation. The service label comes only from local Sage configuration and is
|
|
restricted to a bounded identifier; remote requests cannot select a process,
|
|
unit, or executable. `connect_only` preserves externally managed deployments.
|
|
|
|
When the Linux runtime is configured, the control service executes this
|
|
lifecycle:
|
|
|
|
1. Validate the canonical identifier-only plan against local policy.
|
|
2. Persist the plan and both the controller BLAKE3 digest and Glove SHA-256
|
|
content digest.
|
|
3. Reserve a session for preparation; live host writes are never eligible.
|
|
4. Resolve generation-bound exposures and library bundles through pinned descriptors.
|
|
5. Compose mounts, cgroup limits, output accounting, and an immutable launch
|
|
binding.
|
|
6. Start and recover the child through the executor and reconciler.
|
|
7. Append an authenticated terminal receipt before projecting terminal state.
|
|
|
|
`refinement-eval-v1` is a Glove-owned declarative evaluator on the shipping
|
|
Linux managed-session backend. The model and harness receive no evidence writer.
|
|
Before launch, Glove reads the exact fixture projection through its pinned
|
|
read-only bundle descriptor, strictly decodes a non-executable fixture DSL, and
|
|
removes that supervisor-only projection from the child mount set. The selected
|
|
base or candidate skill projection remains an ordinary immutable read-only
|
|
projection.
|
|
|
|
The PTY drain worker feeds every raw byte to the evaluator before circular
|
|
transcript eviction. After clean drain and resource finalization, Glove checks
|
|
the trusted termination, exit code, wall latency, UTF-8 stream state, and
|
|
required/forbidden transcript literals, then synthesizes the canonical outcome.
|
|
PTY JSON is only text and cannot become evidence. An incomplete stream or
|
|
invalid UTF-8 produces authenticated invalid evidence; resource termination
|
|
overrides otherwise satisfied transcript assertions.
|
|
|
|
The distinct refinement envelope shares the receipt journal's global sequence
|
|
and HMAC head but uses its own receipt and envelope domains. Recovery and audit
|
|
paging preserve the receipt kind, and the session registry refuses a resource
|
|
V1 envelope for a refinement plan. Capability discovery reports schema version
|
|
`1` only when the Linux runtime and protected library projection store were
|
|
both constructed.
|
|
|
|
The local protocol exposes attach, input, resize, signal, detach, stop, and
|
|
cleanup only when the constructed runtime reports `lifecycle_operational()`.
|
|
Pointer presence is not capability evidence. The remote runtime currently
|
|
reports false, schema version `0`, an empty managed-runtime set, and unavailable
|
|
resource mechanisms; every lifecycle dispatch returns `method_not_found`
|
|
without starting SSH. Sage wires only operational lifecycle and receipt
|
|
reconciliation. Exposure create/revoke remains owner-local; peers may
|
|
receive only the redacted catalog. Retained-change inspection and independently
|
|
authorized apply remain separate launch gates.
|
|
|
|
## Construction-only remote backend
|
|
|
|
When `remote_backend` is present, `gloved` validates it as mutually exclusive
|
|
with local materialization and Apple Container configuration. It requires the
|
|
container identity as an untagged canonical `name@sha256:<64 lowercase hex>`
|
|
reference whose suffix exactly equals `container_image_digest`, retains both
|
|
values in the runtime composition, and rejects tags or mismatches. It writes an
|
|
isolated OpenSSH config and pinned `known_hosts` beneath the already verified
|
|
owner-only runtime directory, then constructs the non-operational runtime. This
|
|
step performs no DNS lookup, network probe, authentication, SSH process launch,
|
|
or Docker operation.
|
|
|
|
The generated SSH configuration ignores global known-hosts files, accepts only
|
|
`ssh-ed25519` host keys, disables host-key learning, and disables connection
|
|
sharing with `GlobalKnownHostsFile none`, `HostKeyAlgorithms ssh-ed25519`,
|
|
`UpdateHostKeys no`, `ControlMaster no`, `ControlPath none`, and
|
|
`ControlPersist no`. The forced remote executable accepts exactly `--stdio`,
|
|
sets umask `077`, and reads its expected executable and image SHA-256 digests
|
|
only from the administrator-owned `/etc/glove/remote-executor.identity` source.
|
|
It measures its opened executable and fails closed on missing, empty, malformed,
|
|
writable, or mismatched identity. Before each frame read, it captures the
|
|
monotonic receive start and channel deadline. Request-TTL conversion subtracts
|
|
all frame-receive time and rejects a frame that consumed its TTL; response writes
|
|
use only the resulting request deadline. Only `remote_health` is available and
|
|
it always reports `not_operational`;
|
|
lifecycle methods return `method_not_found`. This is construction evidence, not
|
|
remote-launch readiness.
|
|
|
|
## Library projection
|
|
|
|
Sage plans refer to bundle digests and destination aliases, never arbitrary host
|
|
paths. Local policy maps each alias to a protected sandbox target. Glove opens a
|
|
digest-named bundle beneath an owner-only root without following links, verifies
|
|
its type, ownership, link count, size, identity, and SHA-256 digest, then mounts
|
|
the descriptor as a read-only sandbox file.
|
|
|
|
The launch binding and terminal receipt commit the projection identifier,
|
|
destination, target, and digest. Bundle expansion into harness-native prompt
|
|
directories is not implemented; `prompt_ref` remains rejected.
|
|
|
|
## Persistence
|
|
|
|
Session and receipt journals are append-only and bounded. Recovery rejects
|
|
corrupt, reordered, duplicated, truncated, or externally resized records.
|
|
Authenticated terminal receipts form an HMAC chain anchored by Sage. The
|
|
general JSONL activity log and session-state journal are not protected against a
|
|
same-user host process that can rewrite them.
|
|
|
|
Detailed policy and file invariants are defined in
|
|
[session-policy.md](session-policy.md).
|