Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ golem-skills/**/node_modules/
golem-skills/**/dist/
.DS_Store
/.scala-build/**
proptest-regressions/

# locally kept custom dev files
/.local/
Expand Down
6 changes: 3 additions & 3 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ dependencies = [
dependencies = ["wit"]
description = "Runs unit tests only"
script = '''
cargo-test-r run --workspace --lib -- --nocapture --report-time $JUNIT_OPTS
cargo-test-r run --workspace --exclude golem-schema-derive --lib -- --nocapture --report-time $JUNIT_OPTS
'''

[tasks.dev-tools-tests]
Expand Down Expand Up @@ -616,7 +616,7 @@ env = { "RUST_LOG" = "info", "RUST_BACKTRACE" = "1", "QUIET" = "true" }
script = '''
cargo-test-r run --package integration-tests --test integration :tag: -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
cargo-test-r run --package integration-tests --test integration :tag:group1 -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
cargo-test-r run --package integration-tests --test environment_deletion -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
cargo-test-r run --package integration-tests --test environment-deletion -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
'''

[tasks.integration-tests-group2]
Expand All @@ -634,7 +634,7 @@ env = { "RUST_LOG" = "info", "RUST_BACKTRACE" = "1", "QUIET" = "true", "GOLEM_TE
script = '''
cargo-test-r run --package integration-tests --test integration :tag: -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
cargo-test-r run --package integration-tests --test integration :tag:group1 -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
cargo-test-r run --package integration-tests --test environment_deletion -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
cargo-test-r run --package integration-tests --test environment-deletion -- --nocapture --test-threads=1 --report-time $JUNIT_OPTS
'''

[tasks.integration-tests-group4]
Expand Down
16 changes: 13 additions & 3 deletions cli/golem-cli/command-output-schema/command-output.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5546,7 +5546,11 @@
},
"SecretSpec": {
"type": "object",
"properties": { "category": { "type": "string" } },
"required": ["inner"],
"properties": {
"inner": { "$ref": "#/definitions/SchemaType" },
"category": { "type": "string" }
},
"additionalProperties": false
},
"QuotaTokenSpec": {
Expand Down Expand Up @@ -5796,8 +5800,14 @@
},
"SecretValuePayload": {
"type": "object",
"required": ["secretRef"],
"properties": { "secretRef": { "type": "string" } },
"required": ["secretId", "version", "resolvedAt"],
"properties": {
"secretId": { "type": "string" },
"configKey": { "type": "array", "items": { "type": "string" } },
"version": { "type": "integer", "minimum": 0 },
"resolvedAt": { "type": "string" },
"category": { "type": "string" }
},
"additionalProperties": false
},
"QuotaTokenValuePayload": {
Expand Down
6 changes: 5 additions & 1 deletion cli/golem-cli/src/agent_id_display/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,11 @@ fn capability_values_render_as_redacted_in_every_language() {

let secret_ty = SchemaType::secret(SecretSpec::default());
let secret_val = SchemaValue::Secret(SecretValuePayload {
secret_ref: "shhh-do-not-log".to_string(),
secret_id: uuid::Uuid::nil(),
config_key: Some(vec!["shhh-do-not-log".to_string()]),
version: 0,
resolved_at: chrono::DateTime::from_timestamp(0, 0).unwrap(),
category: None,
});
let secret_graph = SchemaGraph::anonymous(secret_ty.clone());

Expand Down
31 changes: 31 additions & 0 deletions cli/golem-cli/tests/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,37 @@ fn test_binary_profile() -> String {
}

fn test_binary_path(profile: &str, binary_name: &str) -> PathBuf {
if let Ok(current_exe) = std::env::current_exe()
&& let Some(dir) = current_exe.parent()
{
let current_profile_dir = if dir.file_name() == Some(OsStr::new("deps")) {
dir.parent()
} else {
Some(dir)
};
if let Some(target_dir) = current_profile_dir.and_then(Path::parent) {
let profile_dir = target_dir.join(profile);
let path = profile_dir.join(format!("{binary_name}{}", std::env::consts::EXE_SUFFIX));
if path.exists() {
return path;
}
}
}

if let Some(path) = std::env::var_os("CARGO_MAKE_CRATE_TARGET_DIRECTORY")
.or_else(|| std::env::var_os("CARGO_TARGET_DIR"))
.map(PathBuf::from)
.map(|target_dir| {
target_dir.join(format!(
"{profile}/{binary_name}{}",
std::env::consts::EXE_SUFFIX
))
})
.filter(|path| path.exists())
{
return path;
}

let path = workspace_path().join(format!(
"target/{profile}/{binary_name}{}",
std::env::consts::EXE_SUFFIX
Expand Down
18 changes: 8 additions & 10 deletions cli/golem-cli/wit/deps/golem-core-v2/golem-core-v2.wit
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ interface types {
/// representation at the boundary.
resource quota-token;

/// An unforgeable handle to sensitive material held by the runtime. The
/// handle is opaque to guests: components can pass it through schema values
/// and reveal it only through capability-gated host interfaces.
resource secret;

// ============================================================
// Schema graph (self-contained type carrier)
// ============================================================
Expand Down Expand Up @@ -424,6 +429,8 @@ interface types {
// --- Capability nodes ---

record secret-spec {
/// Revealed payload type carried by this secret handle.
inner: type-node-index,
/// Optional categorisation (e.g., `"api-key"`, `"oauth-token"`).
category: option<string>,
}
Expand Down Expand Up @@ -494,7 +501,7 @@ interface types {
union-value(union-value-payload),

// Capability nodes
secret-value(secret-value-payload),
secret-value(own<secret>),
quota-token-handle(own<quota-token>),

// WASI P3 stubs (parseable only in the schema; no constructible values).
Expand Down Expand Up @@ -546,15 +553,6 @@ interface types {
body: value-node-index,
}

/// Capability value: secret transport is **by reference**. The schema side
/// declares the secret; the value side carries an opaque reference that the
/// authority resolves on read. The literal secret material never crosses
/// this carrier.
record secret-value-payload {
/// Opaque, authority-resolved reference.
secret-ref: string,
}

// ============================================================
// Wire carriers
// ============================================================
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/next/develop/config-and-secrets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Secrets are a special type of configuration — they are stored per environment

### Accessing secrets

Unlike regular configuration values, secrets must be retrieved by calling `.get()` on the `Secret` field. This ensures you always get the latest value, even if the secret has been updated since the agent started.
Unlike regular configuration values, secret fields hold an opaque handle until the agent explicitly calls `.get()` on the `Secret` field. The reveal happens at that call site, so secret material is not embedded in the typed config value. Each reveal pins the secret revision it resolved; retries and replay use that pinned revision deterministically, while a fresh `.get()` after a secret update can observe the new value.

### Managing secrets via CLI

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

Secrets are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed at runtime via `@config.Secret[T]` from the Golem MoonBit SDK. Unlike regular config fields, secrets are **not** stored in `golem.yaml` (which is checked into source control). They are managed via the CLI or through `secretDefaults` for local development.
Secrets are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed at runtime via `@config.Secret[T]` from the Golem MoonBit SDK. Unlike regular config fields, the config value carries an opaque secret handle and plaintext is revealed only when agent code calls `.get!()`. Secrets are **not** stored in `golem.yaml` (which is checked into source control). They are managed via the CLI or through `secretDefaults` for local development.

## Declaring Secrets in a Config Struct

Expand Down Expand Up @@ -53,7 +53,7 @@ pub fn MyAgent::connect(self : Self) -> String {

## Reading Secrets at Runtime

Call `.get!()` on a `Secret[T]` to retrieve the value. Secrets are lazily loaded, so updated values are picked up on the next `.get!()` call:
Call `.get!()` on a `Secret[T]` to explicitly reveal the value. Secrets are lazily loaded, so a fresh `.get!()` call can observe updated values after the update is visible:

```moonbit
pub fn MyAgent::connect(self : Self) -> String {
Expand Down Expand Up @@ -105,6 +105,7 @@ secretDefaults:
- The `--secret-type` argument accepts type names using the project's language syntax (e.g., `String`, `UInt`, `Bool`) or JSON-encoded analysed types as a fallback
- Secrets are stored **per-environment**, not per-agent-instance
- Missing required secrets cause agent creation/deployment to fail
- Secrets are lazily loaded on `.get!()`, allowing runtime updates without restarting the agent
- Secrets are revealed on `.get!()`, and each reveal pins the resolved secret revision for deterministic retries and replay
- Fresh `.get!()` calls can observe runtime updates without restarting the agent
- `.get!()` raises `@common.AgentError` — use MoonBit's error handling (`!` or `try`) accordingly
- **Never edit generated files** — `golem_reexports.mbt`, `golem_agents.mbt`, and `golem_derive.mbt` are auto-generated by `golem build`
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

Secrets are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed at runtime via `Secret<T>` from `golem_rust::agentic::Secret`. Unlike regular config fields, secrets are **not** stored in `golem.yaml` (which is checked into source control). They are managed via the CLI or through `secretDefaults` for local development.
Secrets are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed at runtime via `Secret<T>` from `golem_rust::agentic::Secret`. Unlike regular config fields, the config value carries an opaque secret handle and plaintext is revealed only when agent code calls `.get()`. Secrets are **not** stored in `golem.yaml` (which is checked into source control). They are managed via the CLI or through `secretDefaults` for local development.

## Declaring Secrets in a Config Struct

Expand Down Expand Up @@ -75,7 +75,7 @@ impl MyAgent for MyAgentImpl {

## Reading Secrets at Runtime

Call `.get()` on a `Secret<T>` to retrieve the value. Secrets are lazily loaded, so updated values are picked up on the next `.get()` call:
Call `.get()` on a `Secret<T>` to explicitly reveal the value. Secrets are lazily loaded, so a fresh `.get()` call can observe updated values after the update is visible:

```rust
fn connect(&self) -> String {
Expand Down Expand Up @@ -127,5 +127,6 @@ secretDefaults:
- The `--secret-type` argument accepts Rust type names: `String`, `i32`, `bool`, `Vec<String>`, `Option<i32>` (JSON-encoded analysed types like `'{"type":"Str"}'` are also supported as a fallback)
- Secrets are stored **per-environment**, not per-agent-instance
- Missing required secrets cause agent creation/deployment to fail — use `Option<Secret<T>>` for optional secrets
- Secrets are lazily loaded on `.get()`, allowing runtime updates without restarting the agent
- Secrets are revealed on `.get()`, and each reveal pins the resolved secret revision for deterministic retries and replay
- Fresh `.get()` calls can observe runtime updates without restarting the agent
- If you also need non-secret config fields, follow the same `#[agent_config]` constructor pattern
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

**Secrets** are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed via `Secret[T]` from `golem.config`. They are declared inside config case classes alongside regular config fields.
**Secrets** are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed via `Secret[T]` from `golem.config`. They are declared inside config case classes alongside regular config fields, but the config value carries an opaque secret handle; plaintext is revealed only when agent code calls `.get`.

## Declaring Secrets

Expand Down Expand Up @@ -35,7 +35,7 @@ object MyAppConfig {

## Reading Secrets

`Secret[T]` is lazy — call `.get` to fetch the current value:
`Secret[T]` is lazy — call `.get` to explicitly reveal the current value:

```scala
import golem.runtime.annotations.agentImplementation
Expand Down Expand Up @@ -82,7 +82,8 @@ secretDefaults:

## Key Constraints

- `Secret[T]` is lazy — call `.get` to retrieve the actual value
- `Secret[T]` is lazy — call `.get` to reveal the actual value
- Each reveal pins the resolved secret revision for deterministic retries and replay; fresh `.get` calls can observe runtime updates
- Secret values are stored per-environment, not per-agent-instance
- The `Secret[T]` companion provides an implicit `Schema` so `Schema.derived` works on parent case classes
- Missing required secrets cause agent creation to fail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

Secrets are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed via `Secret<T>` from `@golemcloud/golem-ts-sdk`. Unlike regular config fields, secret values are fetched on demand and managed separately from the agent config.
Secrets are sensitive configuration values (API keys, passwords, tokens) stored per-environment and accessed via `Secret<T>` from `@golemcloud/golem-ts-sdk`. Unlike regular config fields, the config value carries an opaque secret handle; plaintext is revealed only when agent code calls `.get()`.

## Declaring Secrets in the Config Type

Expand All @@ -24,7 +24,7 @@ type MyAgentConfig = {

## Using Secrets in Agent Code

Call `.get()` on a `Secret<T>` field to fetch the current value:
Call `.get()` on a `Secret<T>` field to explicitly reveal the current value:

```typescript
import { agent, BaseAgent, Config, Secret } from "@golemcloud/golem-ts-sdk";
Expand Down Expand Up @@ -73,7 +73,8 @@ secretDefaults:

## Key Points

- `Secret<T>` fields are **not** loaded eagerly — call `.get()` to fetch the current value.
- `Secret<T>` fields are **not** loaded eagerly — call `.get()` to reveal the current value.
- Each reveal pins the resolved secret revision for deterministic retries and replay; a fresh `.get()` after an update can observe the new value.
- Secret values are stored **per-environment**, not per-agent-instance.
- Secrets are **not** stored in the `config` section of `golem.yaml` — use `secretDefaults` for dev environments only.
- Missing required secrets cause agent creation to fail.
Expand Down
19 changes: 19 additions & 0 deletions golem-api-grpc/proto/golem/registry/v1/registry_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ service RegistryService {
rpc GetActiveRoutesForDomain (GetActiveRoutesForDomainRequest) returns (GetActiveRoutesForDomainResponse);
rpc GetActiveMcpForDomain (GetActiveMcpForDomainRequest) returns (GetActiveMcpForDomainResponse);
rpc GetCurrentEnvironmentState (GetCurrentEnvironmentStateRequest) returns (GetCurrentEnvironmentStateResponse);
rpc GetAgentSecretRevision (GetAgentSecretRevisionRequest) returns (GetAgentSecretRevisionResponse);

// quotas api
rpc GetResourceDefinitionById (GetResourceDefinitionByIdRequest) returns (GetResourceDefinitionByIdResponse);
Expand Down Expand Up @@ -337,6 +338,24 @@ message GetCurrentEnvironmentStateSuccessResponse {
EnvironmentState environment_state = 1;
}

message GetAgentSecretRevisionRequest {
golem.common.EnvironmentId environment_id = 1;
repeated string path = 2;
uint64 revision = 3;
golem.common.UUID agent_secret_id = 4;
}

message GetAgentSecretRevisionResponse {
oneof result {
GetAgentSecretRevisionSuccessResponse success = 1;
RegistryServiceError error = 2;
}
}

message GetAgentSecretRevisionSuccessResponse {
EnvironmentState environment_state = 1;
}

message GetResourceDefinitionByIdRequest {
golem.common.ResourceDefinitionId resource_definition_id = 1;
}
Expand Down
7 changes: 6 additions & 1 deletion golem-api-grpc/proto/golem/schema/schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ message FieldDiscriminator {

message SecretSpec {
optional string category = 1;
SchemaType inner = 2;
}

message QuotaTokenSpec {
Expand Down Expand Up @@ -388,7 +389,11 @@ message UnionValue {
}

message SecretValue {
string secret_ref = 1;
golem.common.UUID secret_id = 1;
StringList config_key = 2;
uint64 version = 3;
DatetimeValue resolved_at = 4;
optional string category = 5;
}

message QuotaTokenValue {
Expand Down
1 change: 1 addition & 0 deletions golem-common/src/base_model/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub mod error_code {
pub const INVALID_HTTP_METHOD: &str = "INVALID_HTTP_METHOD";
pub const AMBIGUOUS_AGENT_TYPE_NAME: &str = "AMBIGUOUS_AGENT_TYPE_NAME";
pub const CONFLICTING_AGENT_TYPE_NAMES: &str = "CONFLICTING_AGENT_TYPE_NAMES";
pub const AGENT_SECRET_INVALID_CONFIG_TYPE: &str = "AGENT_SECRET_INVALID_CONFIG_TYPE";
pub const AGENT_SECRET_DEFAULT_TYPE_MISMATCH: &str = "AGENT_SECRET_DEFAULT_TYPE_MISMATCH";
pub const AGENT_SECRET_NOT_COMPATIBLE: &str = "AGENT_SECRET_NOT_COMPATIBLE";
pub const AGENT_SECRET_TYPE_CONFLICT: &str = "AGENT_SECRET_TYPE_CONFLICT";
Expand Down
Loading
Loading