arcbox-protocol 0.4.21

Protocol definitions for ArcBox (ttrpc/protobuf)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
// Sandbox service protocol definitions.
//
// A sandbox is a short-lived, strongly-isolated microVM bound to a single
// workload (function, task, or container). Unlike MachineService VMs, a
// sandbox is decoupled from its workload: the initial cmd process exiting does
// NOT destroy the sandbox — it simply transitions back to "ready" and continues
// accepting Run calls until an explicit Stop/Remove or TTL expiry.
//
// Create returns immediately with state "starting"; callers subscribe to
// Events (action="ready") or poll Inspect to learn when the sandbox is usable.
//
// Two services are defined:
//   - SandboxService         : core lifecycle (create / run / exec / stop / remove)
//   - SandboxSnapshotService : checkpoint / restore for cold-start optimisation

syntax = "proto3";

package sandbox.v1;

// =============================================================================
// SandboxService
// =============================================================================

// SandboxService manages short-lived, strongly-isolated microVM sandboxes.
service SandboxService {
    // Create a sandbox and return immediately with state "starting".
    // Subscribe to Events or poll Inspect to wait for state "ready".
    rpc Create(CreateSandboxRequest) returns (CreateSandboxResponse);

    // Run a command inside a sandbox and stream its output.
    // The stream closes with a final RunOutput{done: true} carrying the exit
    // code. The sandbox remains alive after the command exits.
    rpc Run(RunRequest) returns (stream RunOutput);

    // Execute an interactive command inside a sandbox with full stdin support.
    // Send ExecInput{init: ...} as the first message to start the process,
    // then stream ExecInput{stdin: ...} for input and ExecInput{resize: ...}
    // for TTY resize events.
    rpc Exec(stream ExecInput) returns (stream ExecOutput);

    // Stop a sandbox gracefully (waits for any active workload to exit, then
    // shuts down the VM).
    rpc Stop(StopSandboxRequest) returns (Empty);

    // Forcibly destroy a sandbox and release all resources immediately.
    rpc Remove(RemoveSandboxRequest) returns (Empty);

    // Return the current state and metadata of a sandbox.
    rpc Inspect(InspectSandboxRequest) returns (SandboxInfo);

    // List sandboxes, optionally filtered by state or labels.
    rpc List(ListSandboxesRequest) returns (ListSandboxesResponse);

    // Subscribe to sandbox lifecycle events.
    // Emitted actions: "created" | "ready" | "running" | "idle" |
    //                  "stopping" | "stopped" | "failed" | "removed"
    rpc Events(SandboxEventsRequest) returns (stream SandboxEvent);

    // Read a file from inside a sandbox as a stream of chunks.
    // The final chunk has done == true. The sandbox must be alive
    // (ready or running). Limited to 256 MiB per file.
    rpc ReadFile(ReadFileRequest) returns (stream FileChunk);

    // Write a file into a sandbox. The first message must carry `open`;
    // subsequent messages stream `chunk` payloads, the last one with
    // done == true. Limited to 256 MiB per file.
    rpc WriteFile(stream WriteFileRequest) returns (Empty);

    // Expose a sandbox port on the host. The guest installs a DNAT rule from
    // a reserved guest port (40000-49999) to the sandbox, and the daemon
    // binds a host listener forwarding into the guest. Removed automatically
    // on Stop/Remove.
    rpc ExposePort(ExposePortRequest) returns (ExposePortResponse);

    // Remove a previously exposed port mapping.
    rpc UnexposePort(UnexposePortRequest) returns (Empty);
}

// =============================================================================
// SandboxSnapshotService
// =============================================================================

// SandboxSnapshotService provides checkpoint / restore for cold-start
// optimisation. A snapshot captures a fully-booted, idle sandbox so that
// future sandboxes can be restored from it instead of booting from scratch.
service SandboxSnapshotService {
    // Checkpoint a sandbox into a reusable snapshot.
    // The sandbox is paused, snapshotted, then resumed automatically.
    // The sandbox must be in "ready" or "idle" state.
    rpc Checkpoint(CheckpointRequest) returns (CheckpointResponse);

    // Restore a new sandbox from a previously created snapshot.
    // The restored sandbox starts in "ready" state; boot time is near-zero.
    rpc Restore(RestoreRequest) returns (RestoreResponse);

    // List all snapshots, optionally filtered by origin sandbox or label.
    rpc ListSnapshots(ListSnapshotsRequest) returns (ListSnapshotsResponse);

    // Delete a snapshot and its on-disk data.
    rpc DeleteSnapshot(DeleteSnapshotRequest) returns (Empty);
}

// =============================================================================
// Common
// =============================================================================

// Empty response / request.
message Empty {}

// =============================================================================
// Resource primitives
// =============================================================================

// Network configuration for a sandbox.
message NetworkSpec {
    // Network mode: "tap" (default, TAP + IP pool) or "none" (no network).
    string mode = 1;
}

// A single bind mount into the sandbox.
message Mount {
    // Source path on the host.
    string source = 1;
    // Target path inside the sandbox.
    string target = 2;
    // Mount read-only.
    bool readonly = 3;
}

// CPU and memory resource limits.
message ResourceLimits {
    // Number of vCPUs (0 = daemon default).
    uint32 vcpus = 1;
    // Memory in MiB (0 = daemon default).
    uint64 memory_mib = 2;
}

// Terminal size for TTY resize events.
message TerminalSize {
    // Terminal width in columns.
    uint32 width = 1;
    // Terminal height in rows.
    uint32 height = 2;
}

// =============================================================================
// CreateSandbox
// =============================================================================

// Request to create a sandbox.
message CreateSandboxRequest {
    // Caller-supplied unique ID for idempotent creation.
    // If empty the daemon generates a UUID.
    string id = 1;

    // Arbitrary key-value metadata (used for filtering in List / Events).
    map<string, string> labels = 2;

    // --- VM image ---
    // Kernel image path (empty = daemon default).
    string kernel = 3;
    // Root filesystem image path (empty = daemon default).
    string rootfs = 4;
    // Kernel command-line arguments (empty = daemon default).
    string boot_args = 5;

    // --- Resources ---
    ResourceLimits limits = 6;

    // --- Initial workload (optional) ---
    // OCI image reference. NOT supported in Sandbox V1: a non-empty value is
    // rejected with FAILED_PRECONDITION. Build the rootfs from a local
    // Docker image instead (CLI --from-image resolves the overlay2 layer and
    // passes it as `rootfs`). Registry pull lands with a rustls-capable
    // oci2rootfs release.
    string image = 7;
    // Initial command launched automatically after boot.
    // Empty = sandbox enters "ready" without running anything.
    // When this process exits the sandbox transitions back to "ready"
    // (NOT destroyed) and continues accepting Run calls.
    repeated string cmd = 8;
    // Environment variables for the initial command.
    map<string, string> env = 9;
    // Working directory for the initial command.
    string working_dir = 10;
    // User to run the initial command as.
    string user = 11;

    // --- Filesystem ---
    // NOT supported in Sandbox V1: a non-empty list is rejected with
    // FAILED_PRECONDITION. Copy files in with WriteFile / `sandbox cp`.
    repeated Mount mounts = 12;

    // --- Network ---
    NetworkSpec network = 13;

    // --- Lifecycle ---
    // Sandbox auto-destruction timeout in seconds (0 = no limit).
    // The timer starts from the moment the sandbox is created and is not
    // reset by workload activity.
    uint32 ttl_seconds = 14;

    // --- Provisioning ---
    // NOT supported in Sandbox V1: a set value is rejected with
    // FAILED_PRECONDITION. Use Exec for interactive access.
    optional string ssh_public_key = 15;

    reserved 16;
}

// Response to CreateSandbox.
// Returned immediately; the sandbox may still be booting (state "starting").
message CreateSandboxResponse {
    // Assigned or caller-supplied sandbox ID.
    string id = 1;
    // IP address pre-allocated for the sandbox (empty if mode = "none").
    // Available even while state is "starting".
    string ip_address = 2;
    // State at time of response: always "starting".
    string state = 3;
}

// =============================================================================
// Run
// =============================================================================

// Request to run a command inside a ready sandbox.
// The sandbox must be in "ready" or "idle" state; if it is "running" the call
// returns FAILED_PRECONDITION.
message RunRequest {
    // Sandbox ID.
    string id = 1;
    // Command and arguments.
    repeated string cmd = 2;
    // Environment variable overrides.
    map<string, string> env = 3;
    // Working directory (empty = rootfs default).
    string working_dir = 4;
    // User to run as (empty = rootfs default).
    string user = 5;
    // Allocate a pseudo-TTY.
    bool tty = 6;
    // Kill the command after this many seconds (0 = no timeout).
    uint32 timeout_seconds = 7;
}

// A single chunk of streaming output from Run.
message RunOutput {
    // Stream name: "stdout" | "stderr" | "exit".
    string stream = 1;
    // Raw output bytes (empty when stream == "exit").
    bytes data = 2;
    // Process exit code. Only valid on the final message (done == true).
    int32 exit_code = 3;
    // True on the last message of the stream.
    bool done = 4;
}

// =============================================================================
// Exec  (bidirectional stream)
// =============================================================================

// A single message sent by the client during an Exec session.
message ExecInput {
    oneof payload {
        // Must be the first message in the stream. Carries command parameters.
        ExecRequest init = 1;
        // Subsequent messages: raw bytes forwarded to the process stdin.
        bytes stdin = 2;
        // Resize the pseudo-TTY. Only valid when ExecRequest.tty == true.
        TerminalSize resize = 3;
    }
}

// Parameters for starting an exec session. Sent as ExecInput{init: ...}.
message ExecRequest {
    // Sandbox ID.
    string id = 1;
    // Command and arguments.
    repeated string cmd = 2;
    // Environment variable overrides.
    map<string, string> env = 3;
    // Working directory.
    string working_dir = 4;
    // User to run as.
    string user = 5;
    // Allocate a pseudo-TTY.
    bool tty = 6;
    // Initial terminal size (required when tty == true).
    TerminalSize tty_size = 7;
    // Kill the command after this many seconds (0 = no timeout).
    uint32 timeout_seconds = 8;
}

// A single chunk of streaming output from Exec.
message ExecOutput {
    // Stream name: "stdout" | "stderr" | "exit".
    string stream = 1;
    // Raw output bytes (empty when stream == "exit").
    bytes data = 2;
    // Process exit code. Only valid on the final message (done == true).
    int32 exit_code = 3;
    // True on the last message of the stream.
    bool done = 4;
}

// =============================================================================
// File I/O
// =============================================================================

// Request to read a file from a sandbox.
message ReadFileRequest {
    // Sandbox ID.
    string id = 1;
    // Absolute path inside the sandbox rootfs.
    string path = 2;
}

// One chunk of file data in a ReadFile / WriteFile stream.
message FileChunk {
    // Raw bytes (may be empty on the final chunk).
    bytes data = 1;
    // True on the last chunk of the stream.
    bool done = 2;
}

// Opens a WriteFile stream. Must be the first WriteFileRequest message.
message WriteFileOpen {
    // Sandbox ID.
    string id = 1;
    // Absolute destination path inside the sandbox rootfs.
    string path = 2;
    // Unix permission bits for the created file (0 = 0644).
    uint32 mode = 3;
}

// A single client message in a WriteFile stream.
message WriteFileRequest {
    oneof payload {
        // Stream header — sandbox, path, and mode.
        WriteFileOpen open = 1;
        // File content chunk; the last one has done == true.
        FileChunk chunk = 2;
    }
}

// =============================================================================
// Port exposure
// =============================================================================

// Request to expose a sandbox port on the host.
message ExposePortRequest {
    // Sandbox ID.
    string id = 1;
    // Port the workload listens on inside the sandbox.
    uint32 sandbox_port = 2;
    // Host port to bind (0 = reuse the allocated guest relay port).
    uint32 host_port = 3;
    // "tcp" (default) or "udp".
    string protocol = 4;
}

// Response to ExposePort.
message ExposePortResponse {
    // Host port the service is reachable on (via loopback).
    uint32 host_port = 1;
    // Reserved-range guest port carrying the DNAT relay.
    uint32 guest_port = 2;
}

// Request to remove an exposed port mapping.
message UnexposePortRequest {
    // Sandbox ID.
    string id = 1;
    // The sandbox port previously passed to ExposePort.
    uint32 sandbox_port = 2;
    // "tcp" (default) or "udp".
    string protocol = 3;
}

// --- agent wire payloads (vsock, not served over gRPC) ---

// Ask the guest agent to DNAT a reserved guest port to a sandbox port.
message SandboxPortForwardRequest {
    // Sandbox ID.
    string id = 1;
    // Destination port inside the sandbox.
    uint32 sandbox_port = 2;
    // "tcp" (default) or "udp".
    string protocol = 3;
}

// Guest agent's answer: the allocated reserved-range guest port.
message SandboxPortForwardResponse {
    // Guest port now DNATed to the sandbox.
    uint32 guest_port = 1;
}

// Ask the guest agent to remove a DNAT mapping.
message SandboxPortForwardRemoveRequest {
    // Sandbox ID.
    string id = 1;
    // The sandbox port previously forwarded.
    uint32 sandbox_port = 2;
    // "tcp" (default) or "udp".
    string protocol = 3;
}

// =============================================================================
// Stop / Remove
// =============================================================================

// Request to stop a sandbox gracefully.
message StopSandboxRequest {
    // Sandbox ID.
    string id = 1;
    // Seconds to wait for any active workload to exit before force-killing
    // the VM (0 = daemon default of 30 s).
    uint32 timeout_seconds = 2;
}

// Request to forcibly remove a sandbox.
message RemoveSandboxRequest {
    // Sandbox ID.
    string id = 1;
    // Force removal even if the sandbox is in "running" state.
    bool force = 2;
}

// =============================================================================
// Inspect / List
// =============================================================================

// Request to inspect a sandbox.
message InspectSandboxRequest {
    // Sandbox ID.
    string id = 1;
}

// Full sandbox state.
//
// State machine:
//
//   starting ──► ready ──► running ──► ready   (cmd/Run exited, sandbox alive)
//                  │          │
//                  └──────────┴──► stopping ──► stopped
//                                       │
//                                    failed
//
// "idle" is an alias for "ready" used in event payloads to signal that a
// previously running workload has just finished.
message SandboxInfo {
    // Sandbox ID.
    string id = 1;
    // State: starting | ready | running | stopping | stopped | failed.
    string state = 2;
    // User-supplied labels.
    map<string, string> labels = 3;
    // Effective resource limits.
    ResourceLimits limits = 4;
    // Network information.
    SandboxNetwork network = 5;
    // Creation timestamp (Unix seconds).
    int64 created_at = 6;
    // Timestamp when the sandbox first became ready (Unix seconds, 0 if not yet).
    int64 ready_at = 7;
    // Timestamp when the last workload exited (Unix seconds, 0 if none has run).
    int64 last_exited_at = 8;
    // Exit code of the last workload (only meaningful when last_exited_at > 0).
    int32 last_exit_code = 9;
    // Human-readable error message (only set when state == "failed").
    string error = 10;
}

// Network details of a sandbox.
message SandboxNetwork {
    // Assigned IP address.
    string ip_address = 1;
    // Gateway address.
    string gateway = 2;
    // TAP interface name on the host.
    string tap_name = 3;
}

// Request to list sandboxes.
message ListSandboxesRequest {
    // Filter by state (empty = all states).
    string state = 1;
    // Filter by labels (all key-value pairs must match).
    map<string, string> labels = 2;
}

// Response to ListSandboxes.
message ListSandboxesResponse {
    repeated SandboxSummary sandboxes = 1;
}

// Lightweight sandbox summary for List.
message SandboxSummary {
    // Sandbox ID.
    string id = 1;
    // State.
    string state = 2;
    // Labels.
    map<string, string> labels = 3;
    // IP address.
    string ip_address = 4;
    // Creation timestamp (Unix seconds).
    int64 created_at = 5;
}

// =============================================================================
// Events
// =============================================================================

// Request to subscribe to sandbox lifecycle events.
message SandboxEventsRequest {
    // Filter by sandbox ID (empty = all sandboxes).
    string id = 1;
    // Filter by event action (empty = all actions).
    string action = 2;
}

// A sandbox lifecycle event.
message SandboxEvent {
    // Sandbox ID.
    string sandbox_id = 1;
    // Action:
    //   "created"  — sandbox record created, VM booting
    //   "ready"    — VM booted, sandbox accepting workloads
    //   "running"  — a workload (cmd or Run) started
    //   "idle"     — active workload exited, sandbox back to ready
    //   "stopping" — Stop called, draining workload
    //   "stopped"  — VM shut down
    //   "failed"   — unrecoverable error
    //   "removed"  — sandbox deleted
    string action = 2;
    // Unix nanoseconds.
    int64 timestamp = 3;
    // Additional context, e.g.:
    //   "exit_code" on "idle" / "stopped"
    //   "error"     on "failed"
    map<string, string> attributes = 4;
}

// =============================================================================
// Checkpoint / Restore
// =============================================================================

// Request to checkpoint a sandbox.
// The sandbox must be in "ready" or "idle" state (no active workload).
message CheckpointRequest {
    // Sandbox ID to checkpoint.
    string sandbox_id = 1;
    // Human-readable label for the snapshot.
    string name = 2;
    // Labels attached to the snapshot.
    map<string, string> labels = 3;
}

// Response to Checkpoint.
message CheckpointResponse {
    // Snapshot ID.
    string snapshot_id = 1;
    // On-disk directory containing vmstate and mem files.
    string snapshot_dir = 2;
    // Creation timestamp (RFC3339).
    string created_at = 3;
}

// Request to restore a sandbox from a snapshot.
//
// Direct-mode (non-jailer) limitation: the snapshot's vmstate records the
// origin sandbox's absolute vsock socket path, so concurrent restores from
// the same snapshot — or restoring while the origin sandbox is still
// running — fail with FAILED_PRECONDITION on the vsock conflict. Jailer
// mode restores into per-sandbox chroots and has no such constraint.
message RestoreRequest {
    // Caller-supplied ID for the new sandbox (empty = auto-generated).
    string id = 1;
    // Source snapshot ID.
    string snapshot_id = 2;
    // Labels to assign to the restored sandbox.
    map<string, string> labels = 3;
    // Assign a fresh TAP interface and IP. Required when running multiple
    // sandboxes restored from the same snapshot concurrently.
    bool network_override = 4;
    // TTL for the restored sandbox in seconds (0 = no limit).
    uint32 ttl_seconds = 5;
}

// Response to Restore.
// The restored sandbox starts in "ready" state immediately.
message RestoreResponse {
    // ID of the newly created sandbox.
    string id = 1;
    // Allocated IP address.
    string ip_address = 2;
}

// Request to list snapshots.
message ListSnapshotsRequest {
    // Filter by the origin sandbox ID (empty = all).
    string sandbox_id = 1;
    // Filter by labels.
    map<string, string> labels = 2;
}

// Response to ListSnapshots.
message ListSnapshotsResponse {
    repeated SnapshotSummary snapshots = 1;
}

// Lightweight snapshot summary.
message SnapshotSummary {
    // Snapshot ID.
    string id = 1;
    // ID of the sandbox that was checkpointed.
    string sandbox_id = 2;
    // Human-readable label.
    string name = 3;
    // Labels.
    map<string, string> labels = 4;
    // On-disk directory.
    string snapshot_dir = 5;
    // Creation timestamp (RFC3339).
    string created_at = 6;
}

// Request to delete a snapshot.
message DeleteSnapshotRequest {
    // Snapshot ID.
    string snapshot_id = 1;
}