Skip to content

feat(agent): add PSU agent functionality#1840

Merged
Benoît Cortier (CBenoit) merged 4 commits into
masterfrom
psu-devoagent-poc
Jul 2, 2026
Merged

feat(agent): add PSU agent functionality#1840
Benoît Cortier (CBenoit) merged 4 commits into
masterfrom
psu-devoagent-poc

Conversation

@llervikdevo

Copy link
Copy Markdown
Contributor

POC: Adding ability to Devolutions Agent to connect to PSU over GRPC and start child processes to run PSRP requests from PSU.

@github-actions

Copy link
Copy Markdown

Let maintainers know that an action is required on their side

  • Add the label release-required Please cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module) when you request a maintainer to cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module)

  • Add the label release-blocker Follow-up is required before cutting a new release if a follow-up is required before cutting a new release

  • Add the label publish-required Please publish libraries (`Devolutions.Gateway.Utils`, OpenAPI clients, etc) when you request a maintainer to publish libraries (Devolutions.Gateway.Utils, OpenAPI clients, etc.)

  • Add the label publish-blocker Follow-up is required before publishing libraries if a follow-up is required before publishing libraries

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a POC “PSU gRPC agent” capability to Devolutions Agent: the agent can connect to a PSU gRPC endpoint, register itself/capabilities, and execute PSU-requested work by spawning child processes and streaming stdin/stdout over the gRPC stream.

Changes:

  • Introduces a new psu_grpc_agent task that maintains a gRPC connection to PSU with reconnect/backoff and handles server commands.
  • Implements child-process spawning plus stream multiplexing to forward stdin → process and stdout/stderr → PSU.
  • Adds configuration schema + build-time proto generation + container helpers (Dockerfile/entrypoint + run script).

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
devolutions-agent/src/service.rs Registers the new PSU gRPC agent task when enabled in config.
devolutions-agent/src/psu_grpc_agent/mod.rs Implements the gRPC agent task, registration message, connection loop, and message handling.
devolutions-agent/src/psu_grpc_agent/process.rs Adds process spawning and stream pumping logic between PSU and child processes.
devolutions-agent/src/main.rs Marks new proto/gRPC-related crates as used in the binary crate.
devolutions-agent/src/lib.rs Exposes the new psu_grpc_agent module from the agent library.
devolutions-agent/src/config.rs Adds PsuGrpcAgent configuration DTO + default + deserialization test.
devolutions-agent/proto/psu_agent.proto Defines the POC gRPC protocol (AgentControl bidi stream + messages).
devolutions-agent/Cargo.toml Adds tonic/prost dependencies and build-deps for proto compilation.
devolutions-agent/build.rs Generates Rust code from the PSU agent proto using tonic-build and vendored protoc.
devolutions-agent/Dockerfile.psu-grpc-poc Adds a POC image to build/run the agent alongside PowerShell.
devolutions-agent/docker/psu-grpc-entrypoint.sh Writes a minimal agent config from env vars and starts the agent in the container.
devolutions-agent/Run-PsuGrpcAgentContainer.ps1 Convenience script to build/run the POC container with env configuration.
Cargo.lock Pulls in new dependency graph for tonic/prost/tooling.
.dockerignore Ignores node_modules and .vs in Docker build context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +75
let server_url = conf
.server_url
.as_ref()
.context("PsuGrpcAgent is enabled but ServerUrl is not configured")?
.to_string();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule is just that we don’t capitalize the first letter so it’s easier to compose, but we don’t need to lowercase acronyms, etc: PSU gRPC Agent enabled but server_url is not configured would be good

Comment on lines +42 to +48
if let Some(sender) = sender {
let end_of_stream = stream_data.end_of_stream;
let stream_id = stream_data.stream_id.clone();
if sender.send(stream_data).await.is_ok() && end_of_stream {
self.close_stream(&stream_id).await;
}
}
Comment on lines +195 to +205
let _ = outgoing_tx
.send(agent_message(
&agent_id,
&connection_id,
AgentPayload::StreamClosed(stream_closed(
request.stream_id.clone(),
"child process completed".to_owned(),
false,
)),
))
.await;
Comment thread devolutions-agent/Dockerfile.psu-grpc-poc Outdated
Comment thread devolutions-agent/src/psu_grpc_agent/mod.rs
Hardens the PSU gRPC agent POC and adds optional AppToken support for
authenticating the agent to PowerShell Universal. The POC container also
avoids running as root and no longer disables TLS verification during
Docker builds.

This keeps unauthenticated local development possible when no AppToken
is configured, while allowing PSU deployments to pass an application
token through agent configuration or the container helper.

Issue: N/A

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@CBenoit Benoît Cortier (CBenoit) marked this pull request as draft July 1, 2026 11:16
@CBenoit Benoît Cortier (CBenoit) changed the title adding PSU agent functionality (POC) feat(agent): add PSU agent functionality Jul 1, 2026

@CBenoit Benoît Cortier (CBenoit) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this graduates from POC, I think we should pull the gRPC/protobuf layer out of devolutions-agent into a dedicated crate (or two). Right now tonic/prost are woven through the agent crate in a few places:

  • Cargo.toml declares prost, prost-types, tonic, plus tonic-build + protoc-bin-vendored build-deps.
  • build.rs runs the tonic_build codegen (and the set_var("PROTOC", ...) dance, which we can drop separately via Config::protoc_executable).
  • psu_grpc_agent/mod.rs pulls in the generated code with tonic::include_proto!(...) and leaks prost_types::Timestamp / tonic::{Request, metadata, transport::Endpoint} into the module.
  • process.rs threads the generated prost message types (StartProcess, StreamData, AgentMessage, …) through the whole process-management path.

I propose we split into:

  1. psu-agent-proto: owns proto/psu_agent.proto, the codegen in build.rs, and the generated module; re-exports the messages/client. Contains prost/tonic-build/protoc-bin-vendored in one place (and keeps the protoc handling isolated).
  2. psu-agent-client: wraps the endpoint/auth/reconnect logic behind a domain-friendly API so the rest of the agent never touches tonic types.

devolutions-agent would then depend on those crate(s) instead of prost/tonic directly, and main.rs wouldn't need the use prost as _; use tonic as _; stubs.

One caveat though, the generated prost types currently flow deep into process.rs, so for real containment we want small internal DTOs at the boundary and convert at the edge. Otherwise the split mostly relocates the dependency instead of containing it. Fine as a follow-up rather than blocking this draft. We could merge fast, and I can handle that part if you want.

Comment on lines +9 to +16
let protoc = protoc_bin_vendored::protoc_bin_path().expect("failed to locate vendored protoc");
// SAFETY: Build scripts run single-threaded for this crate before prost-build reads PROTOC.
unsafe { std::env::set_var("PROTOC", protoc) };

tonic_build::configure()
.build_transport(false)
.compile_protos(&["proto/psu_agent.proto"], &["proto"])
.expect("failed to compile PSU agent proto");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: It’s suspicious, I don’t think we need the unsafe { std::env::set_var("PROTOC", protoc) };.

suggestion: The clean way seems to be somewhere in these lines:

@CBenoit Benoît Cortier (CBenoit) marked this pull request as ready for review July 2, 2026 11:50
@CBenoit Benoît Cortier (CBenoit) enabled auto-merge (squash) July 2, 2026 11:52
@CBenoit

Copy link
Copy Markdown
Member

I pushed commits to format the code and address Copilot’s comments. Merging now.

@CBenoit Benoît Cortier (CBenoit) merged commit 182abc2 into master Jul 2, 2026
42 checks passed
@CBenoit Benoît Cortier (CBenoit) deleted the psu-devoagent-poc branch July 2, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants