Skip to content
Open
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion crates/rpc/proto/forge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6403,6 +6403,7 @@ message ScoutStreamApiBoundMessage {
mlx_device.MlxDeviceConfigSyncResponse mlx_device_config_sync_response = 12;
mlx_device.MlxDeviceConfigCompareResponse mlx_device_config_compare_response = 13;
ScoutStreamAgentPingResponse scout_stream_agent_ping_response = 14;
ScoutRemoteExecResponse scout_remote_exec_response = 15;
}
}

Expand Down Expand Up @@ -6432,6 +6433,7 @@ message ScoutStreamScoutBoundMessage {
mlx_device.MlxDeviceConfigSyncRequest mlx_device_config_sync_request = 13;
mlx_device.MlxDeviceConfigCompareRequest mlx_device_config_compare_request = 14;
ScoutStreamAgentPingRequest scout_stream_agent_ping_request = 15;
ScoutRemoteExecRequest scout_remote_exec_request = 16;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a very long running request. I am not sure if it fits the "scout stream" model best.

My understanding was a bit of:

  • If we do anything in the main state machine, then ForgeAgentControl would be the mechanism to tell scout what to do
  • If things are outside of the state machine and relatively short lived, then scout stream could be used.

Maybe @chet who introduced scout stream can help figuring out where it fits best.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I thought we were moving towards the stream model because it seemed much cleaner than ForgeAgentControl. Let's see what @chet has to say.

Copy link
Contributor Author

@rahmonov rahmonov Mar 17, 2026

Choose a reason for hiding this comment

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

Hey Matthias, I have created this PR with the polling approach: #590. If we go with the polling approach, I will close this PR. It also includes your other suggestions (file_artifacts, timeouts). Would appreciate your input there.

}
}

Expand Down Expand Up @@ -6493,6 +6495,30 @@ message ScoutStreamAgentPingResponse {
}
}

// ScoutRemoteExecRequest is sent from carbide-api to the scout agent
// to download files and execute a script on the host.
message ScoutRemoteExecRequest {
string component_type = 1;
string target_version = 2;
string script_url = 3;
uint32 timeout_seconds = 4;
// Files to download before running the script.
// Keys are download URLs, values are expected SHA-256 hex checksums.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd make this a repeated FileArtifcat file_artifact (or something like this), and define FileArtifact as required. Then it becomes more obvious what the fields are compared to the map, and we can extend it if further fields are required in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like this, will add.

// Scout will verify each file after download and reject execution
// if any checksum does not match.
map<string, string> download_files = 5;
}

// ScoutRemoteExecResponse is the result of a scout remote execution,
// sent from scout back to carbide-api.
message ScoutRemoteExecResponse {
bool success = 1;
int32 exit_code = 2;
string stdout = 3;
string stderr = 4;
string error = 5;
}

// ScoutStreamConnectionInfo contains information about an
// active scout agent connection.
message ScoutStreamConnectionInfo {
Expand Down Expand Up @@ -6696,4 +6722,4 @@ message DPFStateResponse {

message GetDPFStateRequest {
repeated common.MachineId machine_ids = 1;
}
}
5 changes: 5 additions & 0 deletions crates/scout/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,15 @@ reqwest = { default-features = false, features = [
"rustls-tls",
"stream",
], workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
futures-util = { workspace = true }
prost-types = { workspace = true }
x509-parser = { workspace = true }

[dev-dependencies]
axum = { workspace = true }

[build-dependencies]
carbide-version = { path = "../version" }

Expand Down
19 changes: 19 additions & 0 deletions crates/scout/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,22 @@ pub(crate) async fn create_forge_client(
.map_err(|err| CarbideClientError::TransportError(err.to_string()))?;
Ok(client)
}

// create_http_client builds a reqwest HTTP client configured with the same
// mTLS certificates used for gRPC communication with carbide-api.
pub(crate) fn create_http_client(config: &Options) -> CarbideClientResult<reqwest::Client> {
let root_ca = std::fs::read(&config.root_ca)?;
let root_cert = reqwest::Certificate::from_pem(&root_ca)
.map_err(|e| CarbideClientError::TransportError(e.to_string()))?;

let client_cert = std::fs::read(&config.client_cert)?;
let client_key = std::fs::read(&config.client_key)?;
let identity = reqwest::Identity::from_pem(&[client_cert, client_key].concat())
.map_err(|e| CarbideClientError::TransportError(e.to_string()))?;

reqwest::Client::builder()
.add_root_certificate(root_cert)
.identity(identity)
.build()
.map_err(|e| CarbideClientError::TransportError(e.to_string()))
}
1 change: 1 addition & 0 deletions crates/scout/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ mod discovery;
mod machine_validation;
mod mlx_device;
mod register;
mod remote_exec;
mod stream;

struct DevEnv {
Expand Down
Loading
Loading