Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions docs/docs/designs/drafts/agentcompute.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,15 @@ policy, and the deployment form of the server. Those follow the prototype.
impaired links) between instances in a sandbox.
- An agent can take screenshots of and send keyboard and pointer input to a
guest desktop.
- Sandboxes expire. Nothing an agent forgets outlives its TTL.
- Sandboxes expire unless an explicitly allowlisted operator pins them.
- The vocabulary is small enough to be discoverable through `search_api` and
stable enough that agents' saved programs keep working.

## Non-goals

- Durable workloads. Anything meant to survive the day belongs in OpenTofu
or CAPI, not here.
- Durable workloads. Operator pins can retain an interactive machine while
iterating, but are not backups or reusable images. Durable workloads belong
in OpenTofu or CAPI; reusable images require recipes under `images/`.
- Generality. No second-user abstraction over hypervisors; the two backends
(Incus, Lume) are named and their differences are exposed, not hidden.
- Cluster configuration. Storage pools, cluster-wide networks, and profiles
Expand Down Expand Up @@ -203,13 +204,21 @@ shape is whatever `describe_api` reports from the Go types.

| Capability | Arguments | Returns | Notes |
| --- | --- | --- | --- |
| `sandbox.create` | `name?`, `platform?` (`incus` default, `mac`), `ttl_minutes?` | `{name, platform, expires_at, network}` | Creates an Incus project (or Lume name prefix) named after the sandbox, plus its `default` NAT'd network. Default TTL 240 minutes. |
| `sandbox.list` | — | `list[{name, platform, created_at, expires_at, instances: int}]` | |
| `sandbox.get` | `name` | `{name, platform, created_at, expires_at, instances: list[...], networks: list[...]}` | One call for an agent to re-orient. |
| `sandbox.extend` | `name`, `ttl_minutes` | `{expires_at}` | Extends from now. |
| `sandbox.delete` | `name` | `{}` | Records expiry, then destroys owned resources in dependency order. A partial failure remains discoverable for reaper retry; an unknown sandbox returns `AgentError`. |

Expired sandboxes are deleted by a reaper inside `agentcompute`.
| `sandbox.create` | `name?`, `platform?` (`incus` default, `mac`), `ttl_minutes?`, `pinned?` (false) | `{name, platform, expires_at, pinned, pinned_by, network}` | Creates an Incus project or Lume sidecar. Incus also creates its `default` NAT network. Default TTL 240 minutes; pinning requires an operator identity. |
| `sandbox.list` | — | `{items: list[{name, platform, created_at, expires_at, pinned, pinned_by, instances: int}]}` | |
| `sandbox.get` | `name` | `{name, platform, created_at, expires_at, pinned, pinned_by, instances: list[...], networks: list[...]}` | One call for an agent to re-orient. |
| `sandbox.extend` | `name`, `ttl_minutes` | `{expires_at}` | Extends from now within the existing TTL limit; pins ignore the deadline. |
| `sandbox.pin` | `name`, `pinned` | `{pinned, pinned_by}` | Operator-only. Unpinning restores the existing expiry, which may already have passed. |
| `sandbox.delete` | `name` | `{}` | Records expiry and clears any pin, then destroys owned resources in dependency order. A partial failure remains discoverable for reaper retry; an unknown sandbox returns `AgentError`. |

Expired, unpinned sandboxes are deleted by a reaper inside `agentcompute`.
`sandbox.pin_identities` is an exact subject allowlist, empty by default;
unauthorized pin/create-pinned/unpin calls return `AgentError` without changes.
Both backends persist `pinned`, `pinned_by`, and `pinned_at` alongside expiry:
Incus in `user.agentcompute.*` project config, Lume in its sidecar. Every scan
logs pinned sandboxes at INFO, including the operator and pin timestamp.
Pins survive restart and leave normal operations usable after expiry.
Re-pinning preserves attribution; unpinning clears it without extending TTL.

#### `image` — the curated catalog

Expand Down
30 changes: 28 additions & 2 deletions docs/docs/runbooks/agentcompute-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ tailnet.
The Incus trust entry is the dedicated, unrestricted `agentcompute01` client.
Never substitute `bootstrap-admin`, broaden an image-build or CI certificate, or
reuse one of those identities. The MCP service accepts the static named bearer
identity `omp`; possession of its token grants every currently registered
capability.
identity `omp`; it can manage every sandbox. Pin and unpin additionally require
that subject in `sandbox.pin_identities`, which is empty by default.

The Phase 2 live restricted-certificate check is the evidence for this
exception: the `image-build` certificate saw an empty filtered list for
Expand Down Expand Up @@ -84,6 +84,32 @@ sessions share it. The reaper scans once at startup and every 30 seconds. Do not
run a second durable service or a long-lived STDIO process against the same
sandbox set.

## Retain an interactive sandbox

After installing a version with `sandbox.pin` support, add
`sandbox.pin_identities: [omp]` in the fleet-managed server configuration and
deploy it through the normal configuration workflow. Do not edit the managed
host file. An empty allowlist disables both pin and unpin for every identity;
existing pins remain pinned until explicitly unpinned or deleted.

Discover the signatures with `search_api` and `describe_api`, then create with
`sandbox.create(name="keep", ttl_minutes=60, pinned=True)` or pin an existing
live sandbox with `sandbox.pin(name="keep", pinned=True)`. Incus and Mac
sandboxes use the same policy. Verify `pinned: true` and `pinned_by: "omp"`
in `sandbox.get` or `sandbox.list`. Each startup/30-second scan logs the
sandbox name, operator, and pin timestamp at INFO with `expires_at ignored`.

Pinning does not change `expires_at` or lift the configured TTL maximum.
To release a pin, call `sandbox.pin(name="keep", pinned=False)`.
**If its expiry has already passed, the next scan deletes the sandbox and all
its instances, snapshots, and published images.** Extend it before unpinning
if you need more time. To destroy it immediately, use `sandbox.delete`.
Verify the sandbox disappears from the list after cleanup.

A pin is not a backup or catalog promotion. Reusable images still require
recipes in `GilmanLab/agentcompute/images/`; `instance.publish` images remain
sandbox-local and are deleted with their sandbox.

## Materialize deployment inputs

Keep plaintext in one owner-only temporary directory. The trap removes local
Expand Down