Skip to content
Kitsy Docs Open CNOS

Go Runtime

Go Runtime

CNOS now ships a first-party Go runtime client in packages/go.

The Go client reads the same runtime bootstrap contracts that the Node runtime already understands:

  • __CNOS_GRAPH__
  • __CNOS_PROJECTION__
  • explicit .cnos-server.json
  • autodiscovered .cnos-server.json next to .cnosrc.yml

That means a Go service can consume CNOS values and secrets directly without dropping back to cnos build env -> .env.

When no projection is present, the Go runtime now falls back to native authoring-time resolution from .cnos/ or cnos/. That path understands:

  • .cnosrc.yml anchor discovery
  • git+https://...#ref and git+ssh://...#ref remote roots
  • .cnos-workspace.yml
  • workspace inheritance and optional global roots
  • profile activation and inheritance
  • filesystem values and secrets
  • dotenv layers
  • process env mappings
  • public promotion
  • runtime-derived values
  • manifest vault mappings for environment and local vault secrets

Build the projection

For deployment or local packaging:

Terminal window
cnos build server --profile prod --to .cnos-server.json

For process bootstrap during development:

Terminal window
cnos run -- ./your-go-service

Import the runtime

Current module path:

import cnos "github.com/kitsyai/cnos/packages/go"

Then load the runtime:

runtime, err := cnos.Load(cnos.Options{})
if err != nil {
panic(err)
}

Or use the package-level singleton for the closest Node-style runtime flow:

if err := cnos.Ready(); err != nil {
panic(err)
}
value, ok, err := cnos.Read("value.app.version")
if err != nil {
panic(err)
}
_ = value
_ = ok

Load() checks sources in this order:

  1. ProjectionData passed through LoadProjection
  2. explicit path from cnos.Options{ProjectionPath: ...}
  3. __CNOS_GRAPH__
  4. __CNOS_PROJECTION__
  5. .cnos-server.json in the current working directory
  6. .cnos-server.json beside a discovered .cnosrc.yml
  7. native authoring-time resolution from cnos.Options{Root: ...} or a discovered .cnosrc.yml

If the process already has __CNOS_GRAPH__, __CNOS_PROJECTION__, or a nearby .cnos-server.json, the package singleton bootstraps automatically and Ready() is optional.

Read values and secrets

port, _, err := runtime.Value("server.port")
if err != nil {
panic(err)
}
token, _, err := runtime.Secret("app.token")
if err != nil {
panic(err)
}

The Go runtime reconstructs the same logical keys used in CNOS:

  • value.*
  • secret.*
  • public.*
  • meta.profile
  • meta.workspace
  • meta.cnos_version

Inspect/provenance is also available directly in Go:

inspect, err := runtime.Inspect("value.app.version")
if err != nil {
panic(err)
}
_ = inspect

Derived values stay live

Runtime-dependent derived formulas in the server projection are evaluated at read time, not frozen during build.

Example CNOS config:

app:
origin:
$derive: "https://${value.app.host}:${process.env.PORT}"

In Go:

origin, _, err := runtime.Value("app.origin")
if err != nil {
panic(err)
}

If PORT changes in the host process, the next read re-evaluates the formula.

Custom runtime namespaces

Custom server-only runtime namespaces work the same way as in Node. Register a provider for any namespace that was declared in the CNOS manifest and carried into the server projection.

host := ""
if err := runtime.RegisterRuntimeProvider("request", func(path string) any {
if path == "headers.host" && host != "" {
return host
}
return nil
}); err != nil {
panic(err)
}

The built-in process.* namespace covers env.*, cwd, platform, arch, and pid. process.node.version is still Node-only.

Secret hydration

The Go runtime supports built-in secret-ref providers used by server projections:

  • environment
  • github-secrets
  • local

Remote providers such as GCP Secret Manager, AWS Secrets Manager, HashiCorp Vault, and Azure Key Vault are compiled into the Go binary by registering provider factories:

runtime, err := cnos.Load(cnos.Options{
SecretVaultProviders: []cnos.SecretVaultProviderFactory{gcpFactory},
})

The manifest remains the configuration source for which named vault uses which provider. Go does not dynamically load provider packages from .cnos.yml.

For local vaults, the Go client reuses the same CNOS auth conventions:

  • __CNOS_VAULT_KEY_<VAULT>__
  • ~/.cnos/secrets/sessions/<vault>.json
  • keychain:cnos/<vault>
  • CNOS_SECRET_PASSPHRASE_<VAULT>
  • CNOS_SECRET_PASSPHRASE

It also understands the encrypted secret payload generated by:

Terminal window
cnos run --auth -- ./your-go-service

That lets a Go process start with already-resolved secret values even when it is not re-authenticating to the local vault itself.

Current limits

The remaining Go-specific gaps are:

  • browser/public runtime behavior

Use it as the server-runtime companion to cnos build server, cnos run, local .cnos/ authoring flows, and Git-backed shared config repos.