From dc3b5388c7e389df3e3a2ad33db420a539d5b7fe Mon Sep 17 00:00:00 2001 From: Vitalii Valkov Date: Fri, 14 Aug 2026 00:06:31 +0200 Subject: [PATCH 1/2] Persistence is the environment's field, not a session kind SHELL_ATTACH is removed and its number reserved. Whether a shell outlives its connection is not something a caller asks for: SHELL takes shell_id and shell_cwd, and the workload decides whether the shell they name survives. Environment gains persistent_shells, defaulting to true. Create and Update take it as optional so an unset field means "leave it alone" rather than "off". Workload gains persistent_shells, resolved from the environment at start. The Terminal Proxy reads it from a call it already makes; nothing else needs to resolve an environment to answer the question. --- proto/agynio/api/agents/v1/agents.proto | 6 +++++ proto/agynio/api/gateway/v1/terminal.proto | 4 ++-- proto/agynio/api/runners/v1/runners.proto | 5 ++++ .../terminal_proxy/v1/terminal_proxy.proto | 23 +++++++++++-------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/proto/agynio/api/agents/v1/agents.proto b/proto/agynio/api/agents/v1/agents.proto index 3b7082c..6cc96df 100644 --- a/proto/agynio/api/agents/v1/agents.proto +++ b/proto/agynio/api/agents/v1/agents.proto @@ -627,6 +627,9 @@ message Environment { // Vendor model names a native-mode workload may request, enforced by the LLM // Proxy. Empty means no restriction. Ignored in platform mode. repeated string llm_allowed_models = 15; + // Whether shells in workloads here outlive the connections that reach them. + // Defaults to true. + bool persistent_shells = 16; } message CreateEnvironmentRequest { @@ -644,6 +647,8 @@ message CreateEnvironmentRequest { // Unspecified defaults to PLATFORM. LLMMode llm_mode = 12; repeated string llm_allowed_models = 13; + // Unset defaults to true. + optional bool persistent_shells = 14; } message CreateEnvironmentResponse { @@ -676,6 +681,7 @@ message UpdateEnvironmentRequest { optional LLMMode llm_mode = 12; // Replaces the existing list; an empty list clears the allowlist. repeated string llm_allowed_models = 13; + optional bool persistent_shells = 14; } message UpdateEnvironmentResponse { diff --git a/proto/agynio/api/gateway/v1/terminal.proto b/proto/agynio/api/gateway/v1/terminal.proto index 9da0088..9a1f2f4 100644 --- a/proto/agynio/api/gateway/v1/terminal.proto +++ b/proto/agynio/api/gateway/v1/terminal.proto @@ -25,8 +25,8 @@ message CreateTerminalSessionRequest { // absolute. string sync_root = 5; - // Names the shell. Required when kind is SHELL_ATTACH, ignored otherwise. - // Opaque, ^[A-Za-z0-9_-]{1,64}$. + // Names the shell to attach to. Optional; meaningful only when kind is + // SHELL and the workload keeps shells. Opaque, ^[A-Za-z0-9_-]{1,64}$. string shell_id = 6; // Working directory for the shell. Optional; applied when the shell is diff --git a/proto/agynio/api/runners/v1/runners.proto b/proto/agynio/api/runners/v1/runners.proto index 4a437ed..d1f635d 100644 --- a/proto/agynio/api/runners/v1/runners.proto +++ b/proto/agynio/api/runners/v1/runners.proto @@ -395,6 +395,9 @@ message Workload { int64 allocated_ram_bytes = 14; // Flavor the workload started on. Empty when it started without an environment. string flavor = 26; + // Whether shells in this workload outlive the connections that reach them. + // Resolved from the environment at start and fixed for the workload's life. + bool persistent_shells = 27; // Machine-readable reason for failures when status is WORKLOAD_STATUS_FAILED. // When status is not FAILED, this value may be unset or stale. optional WorkloadFailureReason failure_reason = 15; @@ -436,6 +439,8 @@ message CreateWorkloadRequest { // Present when owner_kind is RUNTIME_OWNER_KIND_AGENT_INSTANCE; absent otherwise. optional string agent_instance_id = 14; string flavor = 15; + // Whether shells in this workload outlive the connections that reach them. + bool persistent_shells = 16; } message CreateWorkloadResponse { diff --git a/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto b/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto index bfab652..c39e6ca 100644 --- a/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto +++ b/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto @@ -13,12 +13,19 @@ service TerminalProxyService { // platform-defined and bound into the ticket at issuance, so a client never // supplies one and cannot change it at attach time. enum SessionKind { + // SHELL_ATTACH (4) was a second interactive kind. Whether a shell outlives + // its connection is now a property of the workload, not a choice the caller + // makes, so SHELL covers both. + reserved 4; + reserved "SESSION_KIND_SHELL_ATTACH"; + // Rejected with InvalidArgument. It does not default to SHELL: silently // defaulting the field that selects a command is how an unintended command // ends up in a ticket. SESSION_KIND_UNSPECIFIED = 0; - // A login shell on a PTY. The PTY closes with the connection. + // A shell on a PTY. Whether it outlives the connection is decided by the + // workload, not by this request. SESSION_KIND_SHELL = 1; // A caller-supplied command on a PTY. Carries no more privilege than SHELL — @@ -29,10 +36,6 @@ enum SessionKind { // separate from stderr so diagnostics cannot corrupt protocol frames. SESSION_KIND_SYNC = 3; - // A PTY on a named shell that outlives the session. Attaches to shell_id, - // creating it when it does not exist. A second session on the same shell - // displaces the first. - SESSION_KIND_SHELL_ATTACH = 4; } message IssueTicketRequest { @@ -54,13 +57,13 @@ message IssueTicketRequest { // resolved against the real filesystem inside the container. string sync_root = 6; - // Names the shell. Required when kind is SHELL_ATTACH, ignored otherwise. - // Client-generated and opaque, matching ^[A-Za-z0-9_-]{1,64}$. + // Names the shell to attach to. Optional; meaningful only when kind is + // SHELL and the workload keeps shells. Client-generated and opaque, + // matching ^[A-Za-z0-9_-]{1,64}$. string shell_id = 7; - // Working directory for the shell. Optional; meaningful only when kind is - // SHELL_ATTACH. Applied when the shell is created and ignored when it - // already exists. Must be absolute. + // Working directory for the shell. Optional; applied when the shell is + // created and ignored when it already exists. Must be absolute. string shell_cwd = 8; } From e7cce1d9b1593c5c1667fb75df1efabced2f842f Mon Sep 17 00:00:00 2001 From: Vitalii Valkov Date: Fri, 14 Aug 2026 01:48:50 +0200 Subject: [PATCH 2/2] Keep SHELL_ATTACH as a deprecated alias Reserving a deleted enum value does not satisfy the breaking check, and it should not: a client still sending SHELL_ATTACH mid-rollout deserves a working shell rather than an InvalidArgument. The value stays, marked deprecated, and the proxy treats it as SHELL -- which now means the same thing, since the workload decides whether the shell persists. --- .../agynio/api/terminal_proxy/v1/terminal_proxy.proto | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto b/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto index c39e6ca..b6944a9 100644 --- a/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto +++ b/proto/agynio/api/terminal_proxy/v1/terminal_proxy.proto @@ -13,12 +13,6 @@ service TerminalProxyService { // platform-defined and bound into the ticket at issuance, so a client never // supplies one and cannot change it at attach time. enum SessionKind { - // SHELL_ATTACH (4) was a second interactive kind. Whether a shell outlives - // its connection is now a property of the workload, not a choice the caller - // makes, so SHELL covers both. - reserved 4; - reserved "SESSION_KIND_SHELL_ATTACH"; - // Rejected with InvalidArgument. It does not default to SHELL: silently // defaulting the field that selects a command is how an unintended command // ends up in a ticket. @@ -36,6 +30,10 @@ enum SessionKind { // separate from stderr so diagnostics cannot corrupt protocol frames. SESSION_KIND_SYNC = 3; + // Deprecated: use SHELL. Whether a shell outlives its connection is a + // property of the workload rather than a choice the caller makes, so SHELL + // covers both. Accepted as an alias for SHELL. + SESSION_KIND_SHELL_ATTACH = 4 [deprecated = true]; } message IssueTicketRequest {