Alloy
Mesh StorageReference

Configuration

Schema for edge-manager.yaml and edge-sync.yaml

The edge client uses two YAML config files. This page is the canonical reference for every supported field.

FileLoaded byPurpose
edge-manager.yamlalloy-edge managerIdentity + cloud connection + (optionally) local process supervision
edge-sync.yamlalloy-edge syncFolder watcher + upload behaviour

In Docker setups, edge-manager.yaml is generated for you (embedded in the downloaded docker-compose.yml) and edge-sync.yaml is delivered from the cloud — local_state is absent. In binary track-folder setups, you write both files yourself.

Durations use Go-style strings (5s, 1m, 72h). Integer shorthands like 30 are not accepted — quote them as "30s".

Looking for a starter template? The Track Folder setup → walks through a minimal working edge-manager.yaml + edge-sync.yaml pair you can copy and adapt.

edge-manager.yaml

All fields are optional at parse time — the binary will load an empty config. In practice you'll always set backend_url (via the Setup page) and seed_state.api_key.

Top-level

FieldTypeDefaultDescription
backend_urlstring (URL)built-in defaultAlloy backend endpoint. Always set this via the Setup page. Also overridable with ALLOY_BACKEND_URL.
state_dirstring (path)/etc/alloy/stateWhere the manager persists provisioned credentials and runtime state. Docker setups remap this to /ros2_ws/config/state. Also overridable with ALLOY_STATE_DIR.
seed_stateobjectInitial identity + transport. Used on first boot; server-pushed state takes over afterwards. See below.
local_stateobjectLocal-owned supervision block. When set, its processes and tags are owned by the local config — the server cannot overwrite them. Required for binary track-folder. See below.

seed_state

FieldTypeDefaultDescription
api_keystringProvisioning key for first-boot registration. Required to register, but can alternatively be pre-written to {state_dir}/api_key or supplied via ALLOY_API_KEY.
edge_idstring/etc/machine-id, then random UUIDHuman-readable device identifier reported to Alloy. Also overridable with ALLOY_EDGE_ID.
tagsmap[string, string]{}Free-form labels applied on registration. Used for fleet filtering once tag-based features ship.
transport.http.poll_secsfloat (seconds)15.0How often the manager checks in with the backend for desired-state updates. Sub-second supported. Non-positive values clamp to the default.
processes[]list[]Fallback processes until the first successful server sync. Same shape as local_state.processes[]. Prefer local_state.processes when you want processes the server cannot override.

local_state

FieldTypeDefaultDescription
tagsmap[string, string]{}Tags the server cannot overwrite. Merged on top of seed_state.tags.
processes[]list[]Processes supervised locally. Shape below.

Each entry in processes[]:

FieldTypeDefaultRequiredDescription
namestringyesIdentifier for logs and reported_state.
commandstringyesShell command to exec. Runs as the user invoking alloy-edge manager.
enabledbooltruenoKill switch. Set false to define a process but not start it.
restartenumon_failurenoalways / on_failure / never.
shellboolfalsenoWrap command in /bin/sh -c for pipes, redirects, $(...) interpolation.
triggerenumnoWhen to start: boot, cron(<expr>), or connectivity. Without a trigger, processes start when apply() runs.
durationdurationnoStop the process after this duration elapses (timed window).
requires_authboolfalsenoHold the process stopped until the backend approves the device. Set true for anything that authenticates to Alloy with the device API key (alloy-edge sync is the canonical example). Diagnostics, local recorders, and anything that doesn't talk to the data plane can leave this off.
filesmap[string, string]noAttached config files — filename → inline UTF-8 content.

edge-sync.yaml

Only input_dir is strictly required. Disk-management fields default to no limit — eviction is FIFO by modification time, oldest first. Files currently uploading or open by another process are never deleted.

Schema version (v0.8+). New configs should start with version: 1. It opts into the current schema — pipeline-based redaction and the cleanup: blocks below — and stops the loader from rewriting the file on every boot. A file with no version: is read as v0 and auto-migrated in memory; run alloy-edge migrate <file> to rewrite it to v1 on disk (a single .bak backup is kept). On read-only mounts (immutable images, ConfigMap volumes) the on-disk rewrite is skipped and the v0 compat shim runs instead — no error.

FieldTypeDefaultDescription
versionint— (v0)Config schema version. Set version: 1 for the current schema (v0.8+). Absent → v0 compat mode + in-memory migration.
input_dirstring (path)Required. Folder watched for new files.
file_patternstring (glob list)*.mcap,*.json,*.jsonlComma-separated globs. A file is scanned if it matches any pattern.
cycle_timeduration1sHow often the watcher scans input_dir.
upload_delayduration30sWait this long after a file's last modification before uploading. Prevents grabbing files still being written.
state_dirstring (path)/etc/alloy/stateWhere the sync process persists upload bookkeeping. Also accepted under the legacy name status_dir.
credentials_dirstring (path)/etc/alloy/stateWhere the sync process reads device credentials written by the manager.
scan_excludelist of strings[]Additional subdirectory basenames to skip during scan. Dot-prefixed dirs (e.g. .alloy-originals) are always skipped on top of this list.
max_concurrent_uploadsint1Cap on parallel uploads in flight.
mcap_require_footerboolfalseWhen true, .mcap files without a written footer are never eligible via the age-based fallback — they stay Waiting indefinitely until the footer appears. Default false falls through to the upload_delay age check.
signed_url_endpointstring/signed-urlBackend path for signed-URL requests. The compiled-in default targets a legacy endpoint — set /lake-signed-url explicitly for Alloy's current R2 / data-lake backend.
metadatamap[string, string]{}Key-value metadata sent as x-goog-meta-* headers on GCS resumable uploads. Ignored for S3/R2 presigned PUT uploads.
txlog_pathstring (path){state_dir}/txlog.ndjsonPath to the NDJSON transaction log that records every upload attempt.
max_folder_sizesizeCap on total folder size. Format: 10GB, 500MB.
max_file_agedurationDelete files older than this. Format: 72h.
max_file_countintCap on the number of files retained on disk.
bwlimitrateOutbound bandwidth cap. Format: 10M, 1G, 500K (also accepts 10MB/s-style values).
upload_typeenumsigned_urlsigned_url (default, via Alloy backend), none (redact-only — run the filter, write the redacted artefact next to the original, never touch the network; requires redaction.enabled: true), or opendal (upload directly to your own cloud — see below).
keep_filesboolDeprecated. Use lifecycle instead. Kept for back-compat readouts.

v0.8 default changes. Two upload/storage defaults flipped in v0.8 — both are reversible:

  • Multipart uploads default on. upload_settings.multipart now defaults to true, so files >5 GiB upload via the multipart protocol (/upload/*) instead of a single signed PUT. Set upload_settings.multipart: false to force the legacy single-PUT path. (Multipart does not yet carry custom upload metadata: — keep the legacy path if you rely on that.)
  • SQLite index store default. The upload bookkeeping store (index_store.backend) defaults to sqlite (was jsonl). On first v0.8 boot an existing txlog.ndjson is migrated into index_store.sqlite and archived to txlog.ndjson.migrated. Set index_store.backend: jsonl to keep the append-only NDJSON store.
  • include_in_storage_cleanup default truefalse. Moved-aside files (in lifecycle.<stage>.move_to) no longer count against the max_folder_size / max_file_age / max_file_count budget by default — see lifecycle below. Set <stage>.cleanup.include_in_storage_cleanup: true to restore the v0.7 behaviour.

lifecycle — what to do with files at each stage

Optional. Replaces the legacy keep_files boolean with per-stage after: actions. Applies whether or not redaction is enabled.

Stage rename (v0.8). The redacted-artefact stage is now lifecycle.transform; the v0.7 name lifecycle.redacted is still accepted as an alias.

lifecycle:
  original:
    after: keep                  # keep | delete | move
  transform:                     # v0.7: `redacted:`
    after: move
    move_to: /var/lib/alloy/redacted
FieldTypeDefaultDescription
original.afterenumkeepWhat to do with the unredacted input after a successful upload. keep (fail-safe; pair with max_folder_size to bound disk) / delete / move.
original.move_tostring (path).alloy-originals/Destination when original.after: move. Defaults to a sibling dot-dir so the scanner skips it on the next cycle. Relative paths resolve against input_dir.
transform.afterenumkeepWhat to do with the redacted artefact after upload. Same values as original.after. (v0.7 name: redacted.after.)
transform.move_tostring (path).alloy-redacted/Destination when transform.after: move. Relative paths resolve against input_dir. (v0.7 name: redacted.move_to.)
<stage>.cleanup.include_in_storage_cleanupboolfalseWhether files in this stage's move_to count against the max_folder_size / max_file_age / max_file_count budget. Default flipped to false in v0.8 (was true in v0.7) — moved-aside files stay out of the cleanup budget unless you opt in.

lifecycle.original.move_to and lifecycle.transform.move_to must differ when both stages use after: move — v0.8 rejects a shared destination at load time, since the original and its redacted artefact would otherwise overwrite each other.

Redaction pipeline — strip or hash fields before upload

Optional. Runs a redaction rules file over each recording before upload — the rules themselves (channel filter, per-topic transforms, metadata mappings) live in that file; this section is the edge-sync.yaml wiring that points at it and sets the I/O knobs. Use lifecycle to control retention/movement of original and redacted files. See Redact for the why-and-when.

In v0.8+ redaction is one step of a pipeline: tap-chain. The v0.7 flat redaction: block is still accepted — it auto-migrates to an equivalent single-step pipeline at runtime (run alloy-edge migrate to rewrite it on disk).

pipeline: is an ordered tap-chain. A file flows top→bottom through the steps; a step whose transform: is set produces a redacted artefact from the rules file. The transform behaviour knobs (failure policy, output compression, audit) live under lifecycle.transform, not on the step.

version: 1

pipeline_trigger: delay-after-close   # delay-after-close (default) | close
pipeline:
  - transform: .alloy/redaction.yaml  # rules file → redacted artefact
    transform_suffix: redacted        # artefact infix; <stem>.redacted.mcap
    upload: true                      # upload the artefact
    original_after: delete            # keep (tap) | delete | move — flow control for the source
    transform_after: keep             # keep | delete | move — disposition of the artefact
    # file_pattern: "**/camera/*.mcap"  # restrict step to matching files; absent → all
    # filter:                           # gate the step on recording content
    #   require_topics: ["/nav/**"]
    #   min_duration: 60s
    #   min_messages: 100
    #   min_size: 10MB
    #   mcap_require_footer: true

lifecycle:
  transform:
    on_rule_error: skip_record        # skip_record | skip_file | pass_original
    on_reader_error: skip_tail        # abort | skip_tail | recover
    output_compression: inherit       # inherit | none | zstd | lz4
    audit:
      jsonl_path: .alloy/state/redaction-audit.jsonl
      embed_in_mcap: true

Each pipeline[] step:

FieldTypeDefaultDescription
transformstring (path)Path to the redaction.yaml rules file. A step with no transform: is route-only (move/drop without rewriting).
transform_suffixstringstep indexInfix inserted between the input stem and extension for the artefact (mission.mcapmission.redacted.mcap).
uploadboolfalseUpload this step's artefact. Requires transform:.
original_afterenumlifecycle.original.afterFlow control for the source at this step: keep (tap — keep it flowing to later steps), delete, or move.
transform_afterenumlifecycle.transform.afterDisposition of this step's artefact: keep / delete / move.
file_patternstring (glob)Restrict the step to matching files (** crosses /). Absent → all files.
filterobjectGate the step on recording content. Keys: mcap_require_footer (bool), require_topics (glob list), min_duration (duration), min_messages (int), min_size (size). A missing footer waits and re-checks next cycle; a content-predicate miss (topics / duration / messages / size) skips the step.

pipeline_trigger (top-level): delay-after-close (default — wait upload_delay after the writer closes the file) or close (act as soon as the file is closed).

The behaviour knobs (on_rule_error, on_reader_error, output_compression, audit) are documented in the legacy tab and apply identically — in the pipeline form they sit under lifecycle.transform.

The flat redaction: block. Still accepted in v0.8 — the loader desugars it to an equivalent single-step pipeline at runtime. Prefer the pipeline form for new configs.

redaction:
  enabled: true
  rules_file: .alloy/redaction.yaml
  on_rule_error: skip_record       # skip_record | skip_file | pass_original
  on_reader_error: skip_tail       # abort | skip_tail | recover
  output_compression: inherit      # inherit | none | zstd | lz4
  output_suffix: redacted          # filename infix; <stem>.redacted.mcap
  audit:
    jsonl_path: .alloy/state/redaction-audit.jsonl
    embed_in_mcap: true
FieldTypeDefaultDescription
enabledboolfalseMaster switch at the edge-sync level. Even with redaction.yaml itself enabled, the redactor is bypassed unless this is true.
rules_filestring (path)Required when enabled: true. Path to the redaction.yaml rules file.
on_rule_errorenumskip_recordWhat to do when a transform rule blows up on a single record. skip_record — drop the bad record and keep filtering (default). skip_file — abort the file; the original stays put and is reported as FilterFailed. pass_original — fail-open: the unredacted file is uploaded. Use pass_original only with explicit operator opt-in.
on_reader_errorenumskip_tailWhat to do when the MCAP reader hits malformed bytes (truncated chunk, missing footer, summary CRC mismatch). Distinct from on_rule_error. abort — fail the file. skip_tail — emit everything up to the bad chunk and stop. recover — best-effort scan past corruption.
dry_runboolDeprecated. Use upload_type: none plus lifecycle.{original,redacted}.after: move to .dry-run/ instead. Still parses with a deprecation warning; the loader expands it to the equivalent overrides. See Dry-run before flipping it on.
output_compressionenuminheritChunk compression for the redacted MCAP. inherit mirrors the input's first-chunk compression. none / zstd / lz4 pin a specific codec.
output_suffixstringredactedInfix inserted between the input's stem and extension for the redacted artefact (e.g. mission.mcapmission.redacted.mcap). Local filename and cloud object key stay in lockstep with this value.
output_dirstring (path)Where redacted files go. Unset (default): sibling-file behaviour — <input_dir>/.alloy-redacted/<stem>.<output_suffix>.<ext>. Set: the relative path from input_dir to the original is mirrored under output_dir and the suffix is still applied. Relative paths resolve against input_dir.
quarantine.modeenumdeleteDeprecated. Use lifecycle.original.after (delete / move) instead.
quarantine.dirstring (path){state_dir}/quarantineDeprecated. Use lifecycle.original.move_to instead.
audit.jsonl_pathstring (path)Set to write a JSONL sidecar — one line per redacted file. Unset disables the JSONL sidecar. Independent of embed_in_mcap.
audit.embed_in_mcapbooltrueEmbed the audit summary as an MCAP metadata record named alloy.redaction.audit inside the redacted file, so the file documents itself. Set false only when the rule layout is itself sensitive.

Upload directly to your own cloud (OpenDAL)

OpenDAL uploads land in your own bucket and aren't indexed by Alloy — Replay, SQL, Scenarios, and ROS Graph won't work on them. Choose this path only if data cannot leave your cloud.

Set upload_type: opendal and add an opendal: block. Requires an alloy-edge build with the opendal feature.

FieldUsed byDescription
schemealls3, gcs, azblob, or fs
rootallPrefix path (e.g. /fleet/robot-01/recordings)
buckets3, gcsBucket name
regions3AWS region
endpoints3Custom endpoint (MinIO, R2)
containerazblobAzure container name
account_nameazblobAzure storage account
credential_pathgcsService-account JSON path

Options are passed through to OpenDAL. See the OpenDAL service docs for the complete per-scheme options each scheme supports.

On this page