Skip to content

soul.yml

The config for the agent on a managed host. One file per host, by convention /etc/soul/soul.yml (the binary accepts --config <path>).

The config is needed in pull mode (the daemon holds a long-lived stream to Keeper). In push mode most of the fields go unused: Keeper hands the agent an already-rendered run plan over stdin rather than through persistent configuration. Raw Destiny/Essence never reaches the host in either mode — Keeper does the rendering.

The agent has no auth block and no Vault client. Authentication to Keeper is over mTLS only (a client certificate issued during onboarding). This is a deliberate isolation measure: the agent has no way to read secrets from Vault.

NotationMeaning
stringa UTF-8 string.
intan integer.
booltrue / false.
durationa Go duration (5s / 500ms / 1h30m).
enum{a,b,c}a string from the listed set.
string(host:port)host:port; host is an IP or DNS name, port is 1..65535.
fqdna DNS name (dot-separated labels).
pathan absolute path on the host’s filesystem.

default: — marks a required field.

FieldTypeDefaultPurpose
sidfqdnthe host’s FQDNOptional. Defaults to the host’s FQDN. Rarely overridden: if sid does not match the one the agent’s certificate was issued for, the connection will not pass the TLS layer.

If the host follows the standard /var/lib/soul-stack/ layout, the block can be omitted — the defaults apply.

FieldTypeDefaultPurpose
paths.modulespath/var/lib/soul-stack/modulesA cache of custom modules (plugins).
paths.seedpath/var/lib/soul-stack/seedThe directory of the agent’s mTLS identity (certificate + CA). The private key is generated locally during onboarding and never leaves this directory.

keeper — connection to the Keeper cluster

Section titled “keeper — connection to the Keeper cluster”

A list of endpoints with priorities (a fallback list), a retry policy, failback, and the path to the CA. The agent initiates the connection itself — no inbound ports need to be opened on the host.

keeper:
endpoints:
- host: keeper-1.example.com
event_stream_port: 8443 # mTLS, soul run mode
bootstrap_port: 9442 # server-only TLS, soul init mode
- host: keeper-2.example.com
event_stream_port: 8443
bootstrap_port: 9442
priority: 2 # lower priority = fallback
retry:
max_attempts: 5
backoff: { initial: 1s, max: 30s, jitter: true }
handshake_timeout: 10s
failback:
enabled: true
interval: 1h
spray: 10m
max_apply_size_mb: 8
tls:
ca: /var/lib/soul-stack/seed/ca.crt
FieldTypeDefaultPurpose
keeper.endpointslistA non-empty list of the cluster’s endpoints. At least one.
keeper.endpoints[].hoststringThe Keeper instance’s host (FQDN or IP), shared by both phases. Required.
keeper.endpoints[].event_stream_portint (1..65535)The port of the long-lived stream (mTLS, soul run mode). Required.
keeper.endpoints[].bootstrap_portint (1..65535)The onboarding port (server-only TLS, soul init mode). Explicitly required — there is no silent fallback to another port.
keeper.endpoints[].priorityint (≥1)1The priority (lower = preferred). Orders both phases.
keeper.retry.max_attemptsint (≥1)5How many times in a row to try one endpoint before moving to the next.
keeper.retry.backoff.initialduration1sThe initial interval of the exponential backoff.
keeper.retry.backoff.maxduration30sThe upper bound of the backoff.
keeper.retry.backoff.jitterbooltrueWhether to apply random jitter to the backoff.
keeper.retry.handshake_timeoutduration10sThe timeout for establishing the TLS+gRPC connection to one endpoint.
keeper.failback.enabledbooltrueWhether to return to a higher-priority endpoint after failing down.
keeper.failback.intervalduration1hHow often to try to return.
keeper.failback.sprayduration10mThe amplitude of random spread around interval — protection against a thundering herd across thousands of agents.
keeper.max_apply_size_mbint (MiB, ≥1)8The size ceiling for a single inbound message from Keeper (a batch of rendered tasks). Must be ≥ Keeper’s send limit (listen.grpc.event_stream.max_apply_size_mb in keeper.yml); the defaults on both sides match (8 MiB).
keeper.tls.capathThe cluster’s CA certificate: the agent uses it to validate the server side during the mTLS handshake. The client certificate and key live in paths.seed.

In push mode the keeper block is ignored — the plan arrives over stdin, not over the stream.

FieldTypeDefaultPurpose
soulprint.refresh_intervalduration5mHow often the agent recollects facts about the system and (in pull) sends an update over the stream.

The set of collected facts (OS family, distribution, architecture, package manager, init system, kernel, CPU, memory, network) is fixed by the binary and is not declared in the config.

Applies in pull mode (the daemon cleans the cache periodically). In push, cleanup is handled on the Keeper side.

FieldTypeDefaultPurpose
cleanup.modules_ttl_daysint (days)30How many days an unused module version lives in the cache before deletion.
cleanup.run_intervalduration24hHow often the daemon sweeps the cache.

Behavior is symmetric to Keeper: without logging.file — output to stderr without rotation; with a file — built-in rotation without logrotate.

FieldTypeDefaultPurpose
logging.levelenum{debug,info,warn,error}infoThe log level.
logging.formatenum{json,text}jsonjson for machine processing, text for humans.
logging.filepath— (stderr)The path to the log file. Empty — stderr without rotation.
logging.rotation.max_size_mbint (MB)50The file size before rotation.
logging.rotation.max_age_daysint (≥0)7How many days to keep an archive.
logging.rotation.max_filesint5How many archives to keep.
logging.rotation.compressbooltrueWhether to compress archives.

Publishing the agent’s metrics. Unlike Keeper (where metrics are mandatory), on the agent they are optional: not every host wants to open a port.

metrics:
enabled: true
listen: "127.0.0.1:9091" # loopback by default — not reachable from outside
basic_auth: # optional; needed only when bound off loopback
enabled: true
username: scrape
password_file: /etc/soul/metrics-password # mode 0400, a single line
FieldTypeDefaultPurpose
metrics.enabledbooltrueEnable publishing. With false the listener does not come up.
metrics.listenstring(host:port)127.0.0.1:9091The /metrics address. Loopback by default — not exposed outward.
metrics.basic_auth.enabledboolfalseEnable Basic auth. Needed when bound off loopback (scraping from another host).
metrics.basic_auth.usernamestringThe username. Required with enabled: true.
metrics.basic_auth.password_filepathThe path to the password file (a single line). The source is a file, not Vault: the agent has no Vault client. Plaintext in YAML is forbidden. File permissions are the operator’s responsibility (0400 recommended).
otel:
enabled: true
endpoint: "otel-collector.example.com:4317"

OpenTelemetry is push: when enabled, the agent sends traces to an OTLP receiver itself. In the default distribution the receiving side can be a Keeper instance with OTLP reception enabled.

FieldTypeDefaultPurpose
otel.enabledboolfalseEnable OTLP export.
otel.endpointstring(host:port)The OTLP receiver address (gRPC). Required with enabled: true.
otel.export_metricsboolfalseOptionally push metrics over OTLP. At this stage only traces are exported.

The lifecycle of plugins on the Soul side (custom modules). The structure and defaults are symmetric to Keeper (only the socket directory path differs). Full field table — keeper.yml → plugin_runtime; on the agent socket_dir defaults to /var/run/soul-stack/plugins/.

The structure and defaults are identical to Keeper (enable_signal / enable_inotify / audit_correlation_id), see keeper.yml → hot_reload. On the agent, hot-reload works in pull mode via SIGHUP; in push mode it does not apply (the process is short-lived and there is no file on disk).

In pull mode the daemon re-reads soul.yml on SIGHUP: parse → validation → atomic swap. A validation error leaves the current state untouched.

Without a restart, the following apply: the log level, retry/failback parameters, the fact-collection interval, local-cleanup parameters, and plugin policies.

A restart is needed for: sid, paths (paths.*), Keeper endpoints, the TLS CA, the recv limit, the metrics address/auth, the OTel exporter, and log paths and rotation parameters.

An API/MCP reload path (as on Keeper) is not provided on the agent in the current release. Centralized rollout of soul.yml goes through your config-delivery system; after the new file lands, the agent is sent a SIGHUP.

  • The auth block — the agent authenticates over mTLS, not JWT.
  • Destiny and Essence — they never reach the host raw; Keeper renders them on its side and hands over the finished plan.
  • The bootstrap token — used once during onboarding (from stdin or an environment variable), then not stored.
  • The list of modules and their sources — the module registry lives on Keeper.
  • The version field — the binary’s version is determined by the artifact and is not duplicated in the config.
# sid: redis-1.prod.example.com # optional; defaults to the host's FQDN
paths:
modules: /var/lib/soul-stack/modules
seed: /var/lib/soul-stack/seed
keeper:
endpoints:
- host: keeper-1.example.com
event_stream_port: 8443
bootstrap_port: 9442
- host: keeper-2.example.com
event_stream_port: 8443
bootstrap_port: 9442
- host: keeper-dr.example.com
event_stream_port: 8443
bootstrap_port: 9442
priority: 2
retry:
max_attempts: 5
backoff: { initial: 1s, max: 30s, jitter: true }
handshake_timeout: 10s
failback:
enabled: true
interval: 1h
spray: 10m
tls:
ca: /var/lib/soul-stack/seed/ca.crt
soulprint:
refresh_interval: 5m
cleanup:
modules_ttl_days: 30
run_interval: 24h
logging:
level: info
format: json
rotation: { max_size_mb: 50, max_files: 5, compress: true }
metrics:
enabled: true
listen: "127.0.0.1:9091"
otel:
enabled: true
endpoint: "otel-collector.example.com:4317"
  • keeper.yml — the Keeper config (including auth for operators).
  • Configuration → Overview — secrets, cross-cutting capabilities, hot-reload in brief.
  • Soul — the agent’s role, pull/push modes, onboarding.