Skip to content
Merged
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
1 change: 0 additions & 1 deletion .github/workflows/api-acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ jobs:
- name: Build standalone commands
run: |
OAC_DEV_CORE_BUILD_DIR="$RUNNER_TEMP/oac-core-build" make build-core
"$RUNNER_TEMP/oac-core-build/oac-core-device" --help
"$RUNNER_TEMP/oac-core-build/oac-core-environment-key" --help
- uses: actions/setup-python@v6
with:
Expand Down
6 changes: 3 additions & 3 deletions apps/web/e2e/data/routes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ export function buildDemo(now = Math.floor(Date.now() / 1000), publicUrl = "http
});
const observations = sessions.map((session) => {
const base = { id: session.id, object: "agent.runtime_observation", session_id: session.id, resolved_at: now };
if (session.environment.type === "none") return { ...base, environment_id: null, mode: "none", provider_type: null, instance: { kind: "none", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, cpu: null, memory: null };
if (session.environment.type === "self_hosted") return { ...base, environment_id: session.environment.id, mode: "self_hosted", provider_type: null, instance: { kind: "self_hosted_connection", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, cpu: null, memory: null };
if (session.environment.type === "none") return { ...base, environment_id: null, mode: "none", provider_type: null, instance: { kind: "none", allocation_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, cpu: null, memory: null };
if (session.environment.type === "self_hosted") return { ...base, environment_id: session.environment.id, mode: "self_hosted", provider_type: null, instance: { kind: "self_hosted_connection", allocation_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, cpu: null, memory: null };
const allocation = allocations.find((entry) => entry.session_id === session.id);
const sleeping = allocation.compute_phase === "suspended";
return { ...base, environment_id: session.environment.id, mode: "openai_hosted", provider_type: "docker", instance: { kind: "managed_allocation", allocation_id: allocation.id, device_id: null, connection_generation: null }, lifecycle_state: sleeping ? "sleeping" : "active", status: "observed", reason: null, allocation_created_at: session.created_at, observed_at: now - 2, started_at: session.created_at, cpu: sleeping ? null : { usage_seconds_total: 600 + Math.floor(rand() * 4000), capacity_cores: 2, usage_cores: Number((rand() * 1.6).toFixed(2)), utilization_ratio: null }, memory: sleeping ? null : { usage_bytes: Math.floor((0.4 + rand() * 1.4) * 2 ** 30), limit_bytes: 2 * 2 ** 30 } };
return { ...base, environment_id: session.environment.id, mode: "openai_hosted", provider_type: "docker", instance: { kind: "managed_allocation", allocation_id: allocation.id, connection_generation: null }, lifecycle_state: sleeping ? "sleeping" : "active", status: "observed", reason: null, allocation_created_at: session.created_at, observed_at: now - 2, started_at: session.created_at, cpu: sleeping ? null : { usage_seconds_total: 600 + Math.floor(rand() * 4000), capacity_cores: 2, usage_cores: Number((rand() * 1.6).toFixed(2)), utilization_ratio: null }, memory: sleeping ? null : { usage_bytes: Math.floor((0.4 + rand() * 1.4) * 2 ** 30), limit_bytes: 2 * 2 ** 30 } };
});
// Added after everything else is generated, so the seeded data above does not change.
agents.push(...providerAgentDefinitions.map((definition, index) => ({
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/features/dashboard/runtime-trends.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ function snapshot(at: number, options: {
instance: {
kind: "managed_allocation",
allocation_id: options.allocationId ?? "33333333-3333-4333-8333-333333333333",
device_id: null,
connection_generation: null,
},
lifecycle_state: "active",
Expand Down Expand Up @@ -115,7 +114,7 @@ describe("Runtime live-window trends", () => {
const pending = snapshot(120_000);
pending.observations = [{
...pending.observations[0]!,
instance: { kind: "managed_allocation", allocation_id: null, device_id: null, connection_generation: null },
instance: { kind: "managed_allocation", allocation_id: null, connection_generation: null },
lifecycle_state: "pending",
status: "unavailable",
reason: "allocation_pending",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/features/metrics/sandbox-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { SandboxAllocation } from "@oac/agents-client";
import { hostedObservation, node, session } from "../overview/test-fixtures";
import { hostedRuntimeRows, hostedRuntimeUsage, loadHostedRuntimes, matchesRuntime, runtimeSnapshot } from "./sandbox-runtime";

const none = { ...hostedObservation("plain", "p1"), mode: "none", instance: { kind: "none", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", cpu: null, memory: null } as unknown as ReturnType<typeof hostedObservation>;
const none = { ...hostedObservation("plain", "p1"), mode: "none", instance: { kind: "none", allocation_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", cpu: null, memory: null } as unknown as ReturnType<typeof hostedObservation>;

describe("loadHostedRuntimes", () => {
it("keeps hosted observations, reads their Sessions through their project and bounds the reads", async () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/features/overview/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function hostedObservation(sessionId: string, projectId: string, override
return {
id: sessionId, object: "agent.runtime_observation", session_id: sessionId, resolved_at: 1_000,
environment_id: `env_${sessionId}`, mode: "openai_hosted", provider_type: "docker",
instance: { kind: "managed_allocation", allocation_id: `alloc_${sessionId}`, device_id: null, connection_generation: null },
instance: { kind: "managed_allocation", allocation_id: `alloc_${sessionId}`, connection_generation: null },
lifecycle_state: "active", status: "observed", reason: null, allocation_created_at: 100, observed_at: 1_000, started_at: 400,
cpu: { usage_seconds_total: 10, capacity_cores: 2, usage_cores: 0.5, utilization_ratio: null },
memory: { usage_bytes: 100, limit_bytes: 400 },
Expand Down
5 changes: 0 additions & 5 deletions contracts/agents-api/core.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2816,10 +2816,6 @@ definitions:
format: uuid
type: string
x-nullable: true
device_id:
format: uuid
type: string
x-nullable: true
kind:
enum:
- managed_allocation
Expand All @@ -2829,7 +2825,6 @@ definitions:
required:
- allocation_id
- connection_generation
- device_id
- kind
type: object
v1.RuntimeMemoryObservation:
Expand Down
30 changes: 6 additions & 24 deletions contracts/agents-api/machine-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ Machines call Core under `/api/v1`: sandbox nodes, Runtime daemons, the Sandbox
| `POST agent-daemon/installation`, `POST agent-daemon/installation/claim` | Self-hosted installer | Installation grant | [Installation grant](./environment-executor-credentials.md#installation-grant) |
| `POST agent-daemon/enroll` | Self-hosted daemon | Executor credential | [Enroll a self-hosted daemon](#enroll-a-self-hosted-daemon) |
| `GET agent-daemon/connection?environment_id=` | Self-hosted installer | Executor credential | [Private connection confirmation](./environment-executor-credentials.md#private-connection-confirmation) |
| `POST agent-daemon/bootstrap` | Runtime daemon | Daemon credential | [Daemon bootstrap](#daemon-bootstrap) |
| `GET agent-daemon/device-status?device_id=` | Runtime daemon | Daemon credential | [Device status](#device-status) |
| WebSocket `GET agent-daemon/ws?device_id=&version=` | Runtime daemon | Daemon credential | [Core–Runtime protocol](../../docs/runtime-protocol.md) |
| `POST agent-daemon/bootstrap` | Agent-host Runtime | Agent-host credential | [Daemon bootstrap](#daemon-bootstrap) |
| WebSocket `GET agent-daemon/ws?device_id=&version=` | Agent-host Runtime | Agent-host credential | [Core–Runtime protocol](../../docs/runtime-protocol.md) |
| WebSocket `GET sandbox-link` | Sandbox I/O service (serve peer) and agent-host Runtime (attach peer) | The resource's Serve credential or the agent host's Runtime credential, in the Link Hello | [Sandbox link protocol](../../docs/sandbox-link-protocol.md) |

Every credential travels in an `Authorization: Bearer` header, except on `sandbox-link`, where each peer sends it in its Link Hello after the upgrade. No credential travels in a URL. Core derives the [Link URL](../../docs/configuration.md#changing-the-public-url) from `OAC_PUBLIC_URL`.

The generated [`runtime.openapi.yaml`](./runtime.openapi.yaml) describes only the sandbox-node configuration, enroll and identity routes, the two installation routes and the `sandbox-link` upgrade, whose messages the Sandbox link protocol defines. The `sandbox-node/connect` and `agent-daemon/ws` WebSockets and the daemon bootstrap, device-status, enroll and connection routes are served outside the API router and have no generated schema; this document and the linked contracts are their only definition.
The generated [`runtime.openapi.yaml`](./runtime.openapi.yaml) describes only the sandbox-node configuration, enroll and identity routes, the two installation routes and the `sandbox-link` upgrade, whose messages the Sandbox link protocol defines. The `sandbox-node/connect` and `agent-daemon/ws` WebSockets and the daemon bootstrap, enroll and connection routes are served outside the API router and have no generated schema; this document and the linked contracts are their only definition.

## Credentials

Expand All @@ -33,23 +32,10 @@ The generated [`runtime.openapi.yaml`](./runtime.openapi.yaml) describes only th
| Node credential | The node itself: it generates a secret of 32 to 256 characters without whitespace and registers it at enrollment | `sandbox-node/configuration` with `X-OAC-Node-ID`, `sandbox-node/identity`, `sandbox-node/connect` |
| Installation grant | The `x_agents_core.installation` command of a `self_hosted` Session; short-lived | `agent-daemon/installation` and its `claim` |
| Executor credential | The installation claim, or the Core-key [executor credential routes](./environment-executor-credentials.md) | `agent-daemon/enroll` and `agent-daemon/connection`; after enrollment it is also the Serve credential of the Environment's enrollment on `sandbox-link` |
| Operator device profile | `oac-core-device`, run by an operator with database access | `agent-daemon/bootstrap`, `device-status` and `ws` |
| Agent-host credential | Installation initialization writes the [agent-host identity](../../docs/configuration.md#agent-host-container); Core registers it at startup | `agent-daemon/bootstrap`, `agent-daemon/ws` and `sandbox-link` as an attach peer |

Core keeps only a SHA-256 digest of each token and credential it stores; installation grants are signed and not stored. Credentials are not interchangeable: each works only on its own routes.

### Operator device profile

`oac-core-device` provisions a Runtime device profile directly in the database:

```sh
umask 077
mkdir -p ~/.oac/daemon/default
OAC_DATABASE_URL=... oac-core-device --tenant <tenant-uuid> --name 'engine host' --url https://core.example > ~/.oac/daemon/default/auth.json
oac-daemon connect --profile default
```

`--tenant` is the Project's execution tenant UUID and `--url` Core's origin without a path. The command prints the profile once: `server_url` (the origin plus `/api/v1`), `runtime_id` (the device ID), `runner_credential` and `device_name`. Use a new profile rather than overwriting another device's file, and copy it privately to the same path on a remote host. `oac-core-device --tenant <tenant-uuid> --revoke <device-uuid>` revokes the device: new connections are refused at once, and an open connection closes at its next heartbeat. Core binds Sessions only to the deployment's agent host ([Session assignments](../../docs/runtime-protocol.md#session-assignments)), so a device of this profile runs no Session.

## Node routes

### Read the node configuration
Expand Down Expand Up @@ -100,13 +86,9 @@ The credential is checked before any deployment state, so a rejected credential,

### Daemon bootstrap

`POST /api/v1/agent-daemon/bootstrap` with the daemon credential and `{"device_id": "…"}` returns `device_id`, `workspace_id`, `ws_url` (derived from `OAC_PUBLIC_URL`, never from request headers), `heartbeat_seconds` and `protocol_version`. The daemon then dials `ws_url` as the [Core–Runtime protocol](../../docs/runtime-protocol.md#ownership-and-connection) describes.

### Device status

`GET /api/v1/agent-daemon/device-status?device_id=` with the daemon credential returns `device_id` and `online`, which says whether the device has a live connection to Core.
`POST /api/v1/agent-daemon/bootstrap` with the agent-host credential and `{"device_id": "…"}` returns `device_id`, `workspace_id` (an empty string for the deployment-scoped host), `ws_url` (derived from `OAC_PUBLIC_URL`, never from request headers), `heartbeat_seconds` and `protocol_version`. The daemon then dials `ws_url` as the [Core–Runtime protocol](../../docs/runtime-protocol.md#ownership-and-connection) describes.

The bootstrap, device-status and WebSocket routes share one error body, `{"error": code, "detail": text}`: 400 `missing_params`, `missing_device_id` or `bad_json`; 401 `missing_bearer`, `unknown_device` or `bad_credential`; 403 `wrong_runtime_type`; 500 `internal`; and on the WebSocket 426 `incompatible_version` when `version` is not Core's exact Runtime protocol version.
The bootstrap and WebSocket routes share one error body, `{"error": code, "detail": text}`: 400 `missing_params`, `missing_device_id` or `bad_json`; 401 `missing_bearer`, `unknown_device` or `bad_credential`; 403 `wrong_runtime_type`; 500 `internal`; and on the WebSocket 426 `incompatible_version` when `version` is not Core's exact Runtime protocol version.

### Enroll a self-hosted daemon

Expand Down
2 changes: 0 additions & 2 deletions contracts/agents-api/runtime-observability-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ The list has one row for every Session of every Project that is not deleted, inc
"instance": {
"kind": "managed_allocation",
"allocation_id": "d23ab94e-e40b-45bd-93a2-444f1f74642b",
"device_id": "2e434f4f-76aa-4e54-a707-4757036d90ef",
"connection_generation": null
},
"lifecycle_state": "active",
Expand Down Expand Up @@ -115,7 +114,6 @@ This returns one `RuntimeObservation`, without `disk`. It accepts no query param
| --- | --- |
| `kind` | `managed_allocation`, `self_hosted_connection` or `none`. |
| `allocation_id` | The managed allocation, which identifies the compute of a managed Session; null otherwise. |
| `device_id` | The Runtime device bound to the managed allocation, when there is one; null otherwise. |
| `connection_generation` | Always null: Core does not observe self-hosted connections. |

### `cpu`
Expand Down
1 change: 0 additions & 1 deletion contracts/agents-api/v1/runtime_observations.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ type RuntimeObservation struct {
type RuntimeInstance struct {
Kind string `json:"kind" enums:"managed_allocation,self_hosted_connection,none" binding:"required"`
AllocationID *string `json:"allocation_id" extensions:"x-nullable" binding:"required" format:"uuid"`
DeviceID *string `json:"device_id" extensions:"x-nullable" binding:"required" format:"uuid"`
ConnectionGeneration *string `json:"connection_generation" extensions:"x-nullable" binding:"required" format:"uuid"`
}

Expand Down
Loading
Loading