From 1e8eaafb8a0aa1be01e42476424d6941a9dcede1 Mon Sep 17 00:00:00 2001 From: Stefan Amberger <1277330+snamber@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:10:01 +0000 Subject: [PATCH] Define hosted compute and runner lifecycle contracts Keep guest assignments separate from task execution and preserve existing Cluster routing. Amp-Thread-ID: https://ampcode.com/threads/T-01a1973b-8e6b-7672-92ab-0758b1e54779 Co-authored-by: Amp --- apis/workflows/v1/hosted_compute.proto | 27 +++++++++ apis/workflows/v1/hosted_runner.proto | 79 ++++++++++++++++++++++++++ apis/workflows/v1/workflows.proto | 8 +++ 3 files changed, 114 insertions(+) create mode 100644 apis/workflows/v1/hosted_compute.proto create mode 100644 apis/workflows/v1/hosted_runner.proto diff --git a/apis/workflows/v1/hosted_compute.proto b/apis/workflows/v1/hosted_compute.proto new file mode 100644 index 0000000..625fe4c --- /dev/null +++ b/apis/workflows/v1/hosted_compute.proto @@ -0,0 +1,27 @@ +edition = "2023"; + +package workflows.v1; + +option features.field_presence = IMPLICIT; + +// HostedComputeConfig selects one isolated guest pool for an existing Cluster. +message HostedComputeConfig { + bool enabled = 1; + string region_id = 2; + string variant_id = 3; + // Logical reservations include Pending, Starting, Ready and Draining guests. + // Expired ownership can be replaced before physical cleanup is confirmed. + uint32 max_guests = 4; +} + +// HostedComputeStatus distinguishes logical capacity from unconfirmed physical cleanup. +message HostedComputeStatus { + uint64 config_revision = 1; + uint32 reserved_guests = 2; + uint32 ready_guests = 3; + uint32 draining_guests = 4; + uint32 cleanup_unconfirmed = 5; + bool capacity_failed = 6; + uint64 retry_generation = 7; + string reason = 8; +} diff --git a/apis/workflows/v1/hosted_runner.proto b/apis/workflows/v1/hosted_runner.proto new file mode 100644 index 0000000..8f9b13c --- /dev/null +++ b/apis/workflows/v1/hosted_runner.proto @@ -0,0 +1,79 @@ +// Aggregate guest runner lifecycle. This is not a task or host authorization API. +edition = "2023"; + +package workflows.v1; + +option features.field_presence = IMPLICIT; + +// HostedRunnerEnvelope is framed by a four-byte big-endian length (1..65536). +// Version 1 requires matching assignment/epoch/boot identity and increasing sequence +// numbers in each direction, preserved across reconnects of the same guest boot. +message HostedRunnerEnvelope { + uint32 protocol_version = 1; + string assignment_id = 2; + uint64 assignment_epoch = 3; + string guest_boot_id = 4; + uint64 sequence = 5; + // Exactly one guest observation, host command, or guest acknowledgment. + oneof payload { + HostedRunnerHello hello = 10; + HostedRunnerStatus ready = 11; + HostedRunnerStatus status = 12; + HostedRunnerCommand command = 13; + HostedRunnerAcknowledgment acknowledgment = 14; + HostedRunnerStatus safe_to_stop = 15; + } +} + +// HostedRunnerHello starts every connection; it does not enable intake. +message HostedRunnerHello {} + +// HostedRunnerCommand is idempotent. Drain is irreversible for this guest boot. +message HostedRunnerCommand { + string command_id = 1; + HostedRunnerCommandKind kind = 2; +} + +// HostedRunnerCommandKind contains host-to-guest lifecycle commands only. +enum HostedRunnerCommandKind { + HOSTED_RUNNER_COMMAND_KIND_UNSPECIFIED = 0; + HOSTED_RUNNER_COMMAND_KIND_START_INTAKE = 1; + HOSTED_RUNNER_COMMAND_KIND_DRAIN = 2; + HOSTED_RUNNER_COMMAND_KIND_PROBE = 3; +} + +// HostedRunnerAcknowledgment echoes the command ID and its resulting aggregate state. +// An accepted Drain means intake is closed, not that execution/reporting has finished. +message HostedRunnerAcknowledgment { + string command_id = 1; + HostedRunnerCommandKind kind = 2; + bool accepted = 3; + HostedRunnerStatus status = 4; +} + +// HostedRunnerStatus is local responsiveness, not task progress or assignment renewal. +// Uncertainty is sticky across polling reset/reconnect; time alone cannot clear it. +// SafeToStop requires drain and zero claims/tasks/reports/uncertainty. +message HostedRunnerStatus { + bool initialized = 1; + bool intake_open = 2; + bool draining = 3; + bool idle = 4; + uint32 active_claims = 5; + uint32 active_tasks = 6; + uint32 pending_reports = 7; + bool claim_uncertain = 8; + bool report_uncertain = 9; + bool task_lease_problem = 10; + HostedRunnerBackendState backend = 11; + bool safe_to_stop = 12; + // Increments on activity even between heartbeats, so hosts cannot miss an idle reset. + uint64 activity_generation = 13; +} + +// HostedRunnerBackendState summarizes the most recent task RPC observations. +enum HostedRunnerBackendState { + HOSTED_RUNNER_BACKEND_STATE_UNSPECIFIED = 0; + HOSTED_RUNNER_BACKEND_STATE_REACHABLE = 1; + HOSTED_RUNNER_BACKEND_STATE_UNREACHABLE = 2; +} diff --git a/apis/workflows/v1/workflows.proto b/apis/workflows/v1/workflows.proto index c5bc9a0..16033d5 100644 --- a/apis/workflows/v1/workflows.proto +++ b/apis/workflows/v1/workflows.proto @@ -9,6 +9,7 @@ import "google/protobuf/timestamp.proto"; import "tilebox/v1/id.proto"; import "tilebox/v1/query.proto"; import "workflows/v1/core.proto"; +import "workflows/v1/hosted_compute.proto"; option features.field_presence = IMPLICIT; @@ -27,6 +28,9 @@ message Cluster { // The workflows that are currently deployed to this cluster. Each workflow will only contain one release, // which is the currently deployed release for that workflow on this cluster. repeated Workflow deployed_releases = 5; + // Hosted execution is disabled unless explicitly configured. + HostedComputeConfig hosted_compute = 7; + HostedComputeStatus hosted_compute_status = 8; } // CreateClusterRequest creates a new cluster. @@ -43,6 +47,7 @@ message CreateClusterRequest { (buf.validate.field).string.max_len = 100, (buf.validate.field).string.pattern = "^[A-Za-z0-9-]*$" ]; + HostedComputeConfig hosted_compute = 4; } // GetClusterRequest requests details for a cluster. @@ -71,6 +76,9 @@ message UpdateClusterRequest { features.field_presence = EXPLICIT, (buf.validate.field).string.max_len = 50000 ]; + HostedComputeConfig hosted_compute = 4; + // Required when hosted_compute is present; stale writes are rejected. + uint64 expected_hosted_config_revision = 5 [features.field_presence = EXPLICIT]; } // DeleteClusterRequest deletes an existing cluster.