docs
// Execution Management (EEM)

Config Files

Two YAML files. eem.yaml configures the agent: CLI flags win, then environment variables (the primary name, mostly EREBINE_EEM_*, then its legacy EEM_* spelling), then the file, then built-in defaults, flat scalars only. workspaces.yaml declares the workspaces the agent serves, and takes effect without a restart.

Overview

This page documents the on-host config layer for the EEM agent. The Environment Variables reference covers the matching environment overrides; chat templates are project-scoped JSON managed through the router API and live under Author a Chat Template.

Keyboard: press Alt+E to jump to the next example block, Alt+Shift+E for the previous one. The chord ignores text inputs.

eem.yaml

Loaded from the path supplied by --config, default /etc/erebine/eem.yaml. A missing file is a legitimate "no config" signal, so flag-only and environment-only deployments work without an empty stub.

The file is a flat top-level mapping of scalars (string, integer, boolean). Nested maps and sequences are rejected at boot with invalidConfigFile. Keys are lowercased before lookup.

yaml
# required router_url: https://router.example.com registration_name: eem-prod-us-east-01 # first enrollment only join_key: jk_xxxxxxxxxxxxxxxxxxxxxxxx # optional (defaults shown) credentials_dir: /var/lib/erebine/credentials log_level: info lease_renewal_interval_ms: 10000 shutdown_grace_seconds: 30 max_concurrent_executions: 20 signing_key_path: /var/lib/erebine/agent-signing-key.hex curve_key_path: /var/lib/erebine/agent-curve-key.hex queue_db_path: /var/lib/erebine/state/queue.sqlite project_external_id: proj_optional_external_id

Keys

Every key has a matching environment override under its primary name (for example EREBINE_AGENT_JOIN_KEY, EREBINE_EEM_QUEUE_DB_PATH); the legacy EEM_* spelling is read only when the primary name is unset. The required keys, router_url and registration_name, have no default; the agent refuses to start if either is unset across CLI, environment, and file. join_key is needed only when the agent has no saved enrollment to restore.

KeyDefaultRequiredPurpose
router_url unset Yes HTTPS endpoint of the router used for enrollment and the data plane.
join_key unset First enrollment One-shot enrollment token issued by the router. The agent redeems it only when there is no saved enrollment to restore: on the first start, when agent-enrollment.json (written beside the signing key) or one of the key files is missing or unreadable, or when the router no longer accepts the saved identity. In those cases it refuses to start without one. Once the first enrollment succeeds, the key can be revoked and dropped from the file.
registration_name unset Yes Stable display name the router uses to track this agent across restarts.
credentials_dir /var/lib/erebine/credentials No Directory holding one subdirectory per workspace. A workspace's credentials_ref in workspaces.yaml names a file inside its own subdirectory. See Workspace credentials.
log_level info No Log verbosity. One of trace, debug, info, notice, warning, error.
lease_renewal_interval_ms 10000 No Cadence (milliseconds) for lease renewal messages to the router.
shutdown_grace_seconds 30 No Time the agent waits for in-flight executions to finish on SIGTERM before forcing exit.
max_concurrent_executions 20 No Ceiling on simultaneous tool executions handled by the agent.
signing_key_path /var/lib/erebine/agent-signing-key.hex No Persisted Ed25519 signing-key file used to sign messages to the router.
curve_key_path /var/lib/erebine/agent-curve-key.hex No Persisted Curve25519 key-agreement private-key file. The public half is derived on load.
queue_db_path /var/lib/erebine/state/queue.sqlite No SQLite file backing the outbound dispatch queue.
project_external_id unset No External project id (ws_<hex>) attached to callbacks for project-scoped routing.

Reload Semantics

eem.yaml and the environment are read once, at start. A change to either takes effect on the next restart. There is no reload signal: the agent traps SIGTERM and SIGINT, and both stop it.

Two things are read while the agent runs, so a change to them needs no restart:

  • workspaces.yaml, on every tool call and every lease renewal. See workspaces.yaml for when the router's catalogue catches up.
  • A credential file under credentials_dir, by the call that uses it, so a replaced file is used from the next call. A call already running keeps the credential it read.

The paths to both are settings. Moving workspaces.yaml or the credentials directory is a restart.

workspaces.yaml

The agent cannot learn which workspaces it serves. Enrollment hands it an identity, addresses and a token, and no workspace list, so the operator declares them. The name of each declaration must equal the workspace's name in your project: that string is the only thing the router matches a declaration against. A name that matches nothing binds nothing, and it does it quietly.

Declarations live in their own file because eem.yaml accepts flat scalars only, and a scope block is nested. Default location /etc/erebine/workspaces.yaml, overridden by EREBINE_EEM_WORKSPACES_FILE.

yaml
workspaces: - name: prod-k8s # must equal the workspace name in your project type: kubernetes description: Production cluster domains: [kubernetes] scope: namespaces: [default, kube-system] rate_per_minute: 10 limits: max_concurrent_executions: 4 credentials_ref: prod-k8s

Keys

KeyRequiredPurpose
name Yes The workspace's name in your project. The only axis a declaration binds on.
type Yes One of the six types below. It selects the scope shape.
description No Free text carried to the catalogue so a caller can tell two workspaces apart.
domains No The tool domains this workspace admits. A tool whose domain is outside the list is neither advertised for the workspace nor run in it. Absent or empty, it narrows nothing, and the agent-wide EREBINE_EEM_DOMAINS gate still applies on top.
scope No The type-specific block. See the scope reference below.
limits.max_concurrent_executions No Per-workspace ceiling on simultaneous tool calls, under the agent-wide max_concurrent_executions. A call that arrives while the workspace is at its ceiling is refused, not queued. Absent, only the agent-wide ceiling applies.
credentials_ref No One file name, with no /, in credentials_dir/<name>/. A kubernetes workspace binds it as its kubeconfig and an openstack workspace as its clouds.yaml. See Workspace credentials. The credential material never leaves the host.

Workspace credentials

A workspace that sets credentials_ref runs its credentialed tools on that one file and on nothing else on the host. The file lives at credentials_dir/<name>/<credentials_ref>, owned by the agent's account, mode 0600 or 0400, and not a symlink.

Workspace typeThe file isThe tools get
kubernetes A kubeconfig. Its current context picks the cluster, namespace default and user. KUBECONFIG naming the file. kubectl and helm read it and no other kubeconfig; there is no in-cluster fallback.
openstack A clouds.yaml holding exactly one cloud under clouds. That cloud's auth block picks the project. OS_CLIENT_CONFIG_FILE naming the file and OS_CLOUD naming its one cloud. No other clouds.yaml or secure.yaml is read.

The client runs with only those variables, a fixed PATH, and an empty private home directory that is also its working directory, so nothing in the agent account's home or the agent's working directory is read.

  • A file that is missing, empty, a symlink, or readable by group or other refuses the call with credential_unavailable before anything runs. So does a clouds.yaml that does not hold exactly one cloud.
  • Any other credentialed tool in a workspace that sets credentials_ref -- a cloud CLI, git, a database -- is refused with credential_binding_unsupported rather than run on the agent's own identity. So is every credentialed tool in a workspace of another type that sets one.
  • A workspace without credentials_ref runs its tools on the agent's own identity: whatever its account and working directory reach. Two such workspaces on one agent share it. Run one agent per credential boundary, or bind each workspace.

Generating the file

Write it with erectl rather than by hand, so the names come from your project and cannot be mistyped:

bash
erectl workspaces declare --workspace prod-k8s --workspace erebine-repo erectl workspaces declare --workspace prod-k8s --stdout # preview erectl workspaces declare --remove old-cluster

A refresh keeps the scope already in the file. Narrowing is local work and the project's copy is the wider one, so taking it is opt-in through --reseed-scope. Host-specific values -- a code workspace's repo_roots -- are set on the host that runs the work, and a refresh never removes them.

Writing the file rewrites the document through a YAML parser. Comments, blank lines and key order do not survive. Keep notes somewhere the command does not write.

Reload and the default workspace

An edit takes effect without a restart, on two paths with different guarantees. Enforcement is immediate: the agent rebuilds its manifest from this file on every tool call, so narrowing a scope or removing a workspace binds the very next call. The catalogue converges within about a lease interval (10 seconds by default): the router is told the manifest changed and asks for the new one. The catalogue is what the router offers callers and never what the agent enforces, so a stale catalogue can name a workspace the agent refuses, and can never grant one.

Without this file the agent declares a single workspace named local with an empty scope. That workspace can only match a workspace literally named local, and it delivers no scope to any tool, so a code workspace will not work until you declare one here. A file that exists but declares nothing is not the same thing: it declares zero workspaces and drops the local fallback with them.

A file that exists but cannot be understood stops the agent rather than falling back. Silently ignoring a scope declaration is how a workspace ends up running unscoped.

Workspace Scope

Six types, each with its own scope block. Every key listed below is enforced: a tool reads it and refuses work outside it, before it runs anything.

The If absent column is the one to read first, because it says what a key you have not written does. Nothing changes means the key narrows something the tools already do, so leaving it out leaves them as they are. Tools refuse means the key grants something the tools cannot do without it -- a path, a pod, a command, a directory -- so leaving it out turns those tools off until you declare it. Silence never grants anything.

Any key not listed here is carried through unchanged. The scope block is free-form on purpose, so a workspace type this page does not describe still works.

Code

type: code. A checkout on the agent host that the code tools may read, edit and build. Runs on: Linux and macOS.

KeyValueDefaultIf absentPurpose
repo_roots (required)list of absolute paths-Tools refuseAbsolute paths the code tools may touch; every other path is refused.
denied_pathslist of patterns-Nothing changesPatterns carved back out of the roots, matched case insensitively against the basename when the pattern has no slash.
max_file_byteswhole number of bytes1048576Nothing changesLargest file the read tools will open, in bytes.
max_write_byteswhole number of bytes262144Nothing changesLargest write the edit tools will make, in bytes.
allowed_build_commandsname to command line-Tools refuseNamed command lines the build tool may run; a caller names one and cannot supply its own.
allowed_test_commandsname to command line-Tools refuseNamed command lines the test tool may run.
allowed_format_commandsname to command line-Tools refuseNamed command lines the format tool may run.
allowed_lint_commandsname to command line-Tools refuseNamed command lines the lint tool may run.
git_push_allowedtrue or falsefalseTools refuseWhether the git tool may push at all; anything but true refuses.
protected_brancheslist of patterns-Nothing changesBranch patterns a push may never target, matched case sensitively.
allowed_remoteslist of textoriginNothing changesRemote names a push may target.
rate_per_minutewhole number of requests a minute-Nothing changesRequests a minute this workspace admits, counted by the router before a call reaches the agent. Absent, the deployment-wide setting applies, and then the per-domain default.

Kubernetes

type: kubernetes. Clusters the agent drives through the kubectl, helm and kustomize command line. Runs on: Linux and macOS.

KeyValueDefaultIf absentPurpose
namespaceslist of text-Nothing changesNamespaces a call may target, matched case insensitively against the namespace the tool is about to pass. Most tools put default on the command line when the call omits the argument, so an omitted namespace is checked as default; declare it, or pass a namespace. k8s_apply_manifest, k8s_apply_from_file and helm_history pass no namespace at all when the call omits one, which would leave the target to the current context of the kubeconfig on the agent host, and helm_list and k8s_top_cluster read every namespace: while this key is set those are refused, because neither answer is inside a list. Absent, it narrows nothing.
enabled_resource_kindslist of text-Nothing changesResource kinds a call may touch. Each tool's kind is known without asking the cluster: a fixed-kind tool carries it, k8s_delete_resource takes it as kind, and k8s_wait and the k8s_rollout_* tools take it as the part of resource before the slash. Spellings are compared after lowercasing and dropping the API group, so Pod, pod, pods, po and pods. are one kind, and deploy and Deployment.apps are deployments. A kind outside the built-in table is matched literally, so a custom resource works when both sides spell it the same way; relating two spellings of one would mean asking the cluster. Absent, it narrows nothing.
kube_manifest_pathslist of absolute paths-Tools refuseDirectories on the agent host that hold appliable manifests. k8s_apply_from_file refuses any path outside them, and refuses every path while this key is absent. Each entry must be absolute, and a path that leaves a directory through a symbolic link is refused rather than followed.
kustomize_pathslist of absolute paths-Tools refuseDirectories that hold kustomize overlays. kustomize_build refuses any directory outside them, and refuses every directory while this key is absent. An omitted directory means the first entry, and a relative one is resolved against it.
exec_allowed_podslist of patterns-Tools refusePod name patterns k8s_exec may open a shell in, matched case insensitively against the name the call passes. While this key is absent no pod is permitted.
exec_allowed_commandslist of text-Tools refuseBinaries k8s_exec may run, matched against the first element of the command argv exactly as passed, so /bin/sh and sh are separate entries. While this key is absent no command is permitted. Nothing after the first element is checked, and it does not need to be: kubectl exec runs the argv directly with no shell between, so a separator is an argument rather than a second command. A shell listed here allows everything that shell can run.
scale_replica_capwhole number of replicas-Nothing changesLargest replica count k8s_scale_deployment may ask for. Absent, the count is unbounded.
rate_per_minutewhole number of requests a minute-Nothing changesRequests a minute this workspace admits, counted by the router before a call reaches the agent. Absent, the deployment-wide setting applies, and then the per-domain default.
user_rate_per_minutewhole number of requests a minute, or null-Nothing changesPer-caller share of the workspace ceiling, counted by the router; null means no separate per-caller limit. Absent, the deployment-wide setting applies.

Four of these keys refuse when they are absent. A kubernetes workspace that declares no exec_allowed_pods and no exec_allowed_commands cannot run k8s_exec at all; one that declares no kube_manifest_paths cannot run k8s_apply_from_file; one that declares no kustomize_paths cannot run kustomize_build. Each of those tools takes a pod, a command or a filesystem path straight from the caller, so an undeclared key is a grant rather than a narrowing, and a grant nobody wrote grants nothing. Declare the four keys to restore the tools. namespaces, enabled_resource_kinds and scale_replica_cap stay open when absent, because each narrows something the tools are meant to do rather than granting it.

Declaring namespaces changes what an omitted namespace means. It has to: a list that still admitted "whatever the kubeconfig's current context says" or "every namespace" would not be a narrowing. Most calls are unaffected, because most tools put default on the command line themselves and that is simply checked. The ones that change are k8s_apply_manifest, k8s_apply_from_file and helm_history, which pass no namespace when the call omits one, and helm_list and k8s_top_cluster, which read across every namespace. Pass a namespace from the list; for k8s_top_cluster, which takes none, use k8s_top_pods.

k8s_apply_manifest is the one tool whose target is in the body rather than in an argument, since a document carries its own metadata.namespace. While either narrowing key is set the body is parsed and every document is checked: multi-document YAML, JSON and a List wrapper are all read, a document that names a namespaced kind with no namespace in it and none on the call is refused, and a body that is not valid YAML or JSON is refused rather than passed through. Kinds Kubernetes puts outside any namespace, such as Namespace and ClusterRole, are not bounded by namespaces, because nothing can bound them. With neither key set nothing is parsed and the tool behaves exactly as before.

No key bounds which cluster a call reaches. No tool in the bundle takes a context or a cluster argument and none passes --context, so the cluster is decided entirely by the current context of the kubeconfig the tool runs on. With credentials_ref set, that is the workspace's own kubeconfig and no other; see Workspace credentials.

Docker / Podman / Container

type: container. Containers on the agent host, through whichever of docker, podman or container is installed. Runs on: Linux and macOS.

KeyValueDefaultIf absentPurpose
runtimeone of auto, docker, podman, containerautoNothing changesWhich container runtime to drive; auto takes the first one found on the host, trying container, then docker, then podman on macOS, and podman then docker on Linux. Apple's container is the Mac's native runtime, so auto prefers it there; it is considered on macOS only, and a named runtime that is not installed is refused rather than substituted.
allowed_registrieslist of text-Nothing changesRegistries container_image_pull may pull from, matched against the registry the reference itself names. While this key is set a reference that names none is refused, because what a bare name resolves to is the runtime's own configuration and not part of the reference: write docker.io/library/nginx rather than nginx. Absent, it narrows nothing.
allowed_containerslist of patterns-Nothing changesContainers container_start, container_stop, container_restart and container_rm may touch. Matched against the ID or name exactly as the caller passes it; an ID is never resolved to a name, so list the form your callers use. Absent, it narrows nothing.
allowed_imageslist of patterns-Nothing changesImages container_image_pull and container_image_rm may touch, matched against the reference exactly as passed, tag and digest included. Absent, it narrows nothing.
rate_per_minutewhole number of requests a minute-Nothing changesRequests a minute this workspace admits, counted by the router before a call reaches the agent. Absent, the deployment-wide setting applies, and then the per-domain default.
user_rate_per_minutewhole number of requests a minute, or null-Nothing changesPer-caller share of the workspace ceiling, counted by the router; null means no separate per-caller limit. Absent, the deployment-wide setting applies.

Linux host

type: linux. A Linux host the agent inspects and manages through systemd, the journal and the standard utilities. Runs on: Linux only.

KeyValueDefaultIf absentPurpose
shell_runas_userlist of text-Tools refuseAccounts a shell command may be run as; without this key every run-as request is refused. Account names are compared exactly, because a Unix account name is case-sensitive.
allowed_pathslist of absolute paths-Tools refuseAbsolute directories the twelve path-taking host tools may work under; every path argument must resolve inside one, a relative argument is refused rather than guessed at, and a symlink below a root is refused rather than followed. Without this key none of those tools runs.
denied_pathslist of patterns-Nothing changesPatterns carved back out of the allowed paths; a denial beats an allowance. Matched against the whole absolute path, and a pattern with no slash is also matched against the file name alone, so *.pem means pem files anywhere under the roots and /etc/* means that directory. Absent, it narrows nothing.
allowed_commandslist of text-Nothing changesBinary names, such as systemctl or df, that the host tools may run; each tool is checked against the binary it is built to invoke, so the entries are names and not paths. It does not inspect what a shell command line contains, so it places no limit on shell_exec_irreversible. Absent, it narrows nothing.
rate_per_minutewhole number of requests a minute-Nothing changesRequests a minute this workspace admits, counted by the router before a call reaches the agent. Absent, the deployment-wide setting applies, and then the per-domain default.
user_rate_per_minutewhole number of requests a minute, or null-Nothing changesPer-caller share of the workspace ceiling, counted by the router; null means no separate per-caller limit. Absent, the deployment-wide setting applies.

A linux workspace with no allowed_paths loses twelve tools. linux_file_read, linux_file_stat, linux_file_hash, linux_directory_list, linux_directory_size, linux_find_files, linux_chmod, linux_chown, linux_cp, linux_mv, linux_tar_create and linux_tar_extract take an absolute host path from the caller, and a key that bounds a caller-supplied path grants nothing when it is absent. Declare the directories the workspace is for, for example allowed_paths: ["/srv/app", "/var/log"]. Every other linux tool is unaffected: nothing else in the bundle takes a path.

OpenStack

type: openstack. An OpenStack cloud the agent drives through the openstack command line. Runs on: Linux and macOS.

KeyValueDefaultIf absentPurpose
enabled_serviceslist of text-Nothing changesServices these tools may reach: nova, cinder, neutron, glance or heat. No tool takes a service argument, so each one is checked against the service its own command addresses and the refusal happens before a command line is built. The service is the one the call is routed to rather than the resource in the tool's name: openstack_volume_attach runs server add volume, so it needs nova. Absent, it narrows nothing.
heat_template_pathslist of absolute paths-Tools refuseAbsolute directories openstack_stack_create may read its template and environment files from; both must resolve inside one, a relative argument is refused rather than guessed at, and a symlink below a root is refused rather than followed. Without this key no stack is created.
server_create_flavor_allowlistlist of text-Nothing changesFlavors openstack_server_create and openstack_server_resize may name, compared exactly. Absent, it narrows nothing.
server_create_image_allowlistlist of text-Nothing changesImages openstack_server_create may boot, compared exactly. Absent, it narrows nothing.
server_create_network_allowlistlist of text-Nothing changesNetworks openstack_server_create may attach to, and pools openstack_floatingip_create may allocate an address from, compared exactly. Absent, it narrows nothing.
image_source_pathslist of absolute paths-Tools refuseAbsolute directories openstack_image_upload may read its file from, under the same containment and symlink rules. Without this key no image is uploaded.
rate_per_minutewhole number of requests a minute-Nothing changesRequests a minute this workspace admits, counted by the router before a call reaches the agent. Absent, the deployment-wide setting applies, and then the per-domain default.
user_rate_per_minutewhole number of requests a minute, or null-Nothing changesPer-caller share of the workspace ceiling, counted by the router; null means no separate per-caller limit. Absent, the deployment-wide setting applies.

An openstack workspace with no heat_template_paths creates no stacks, and one with no image_source_paths uploads no images. template, environment and file are absolute host paths straight from the caller, and the client reads every byte behind them, so each key is a grant rather than a narrowing, and a grant nobody wrote grants nothing. Declare the directories the workspace owns, for example heat_template_paths: ["/opt/heat-templates"], to restore those two tools. The other 33 are unaffected: nothing else in the bundle takes a path.

No key names which project a call acts in. The project comes from the one cloud in the clouds.yaml the workspace binds through credentials_ref, not from any tool argument; see Workspace credentials. The project argument on openstack_server_list filters a listing and cannot widen one. Nothing bounds key_name either, so a caller may inject any keypair the project holds.

Infrastructure as code

type: iac. Terraform working directories on the agent host. Runs on: Linux and macOS.

KeyValueDefaultIf absentPurpose
workspace_dirslist of absolute paths-Tools refuseDirectories the terraform tools may run in. Every call must name a directory at or below one of them, and while this key is absent no directory is permitted, so none of the four tools runs. Each entry must be absolute. Containment compares whole components, so /srv/terraform-scratch is outside /srv/terraform, and a directory that is or sits below a symbolic link is refused rather than followed.
rate_per_minutewhole number of requests a minute-Nothing changesRequests a minute this workspace admits, counted by the router before a call reaches the agent. Absent, the deployment-wide setting applies, and then the per-domain default.
user_rate_per_minutewhole number of requests a minute, or null-Nothing changesPer-caller share of the workspace ceiling, counted by the router; null means no separate per-caller limit. Absent, the deployment-wide setting applies.

An iac workspace that declares no workspace_dirs runs no terraform at all. terraform_plan, terraform_apply, terraform_output and terraform_state_list all refuse until the key is declared. The directory argument is a filesystem path straight from the caller, and terraform in a directory is terraform against whatever real infrastructure that directory's state binds, so the key is a grant rather than a narrowing, and a grant nobody wrote grants nothing. Declare the absolute paths the workspace owns, for example workspace_dirs: ["/srv/terraform/prod"], to restore the tools.

All four tools now require a directory, and it must be absolute. It used to be optional, and an omitted one ran terraform in whichever directory the agent process was started in. Nothing took that default's place: a workspace may declare several directories, and applying, or reading the state of, the wrong one is somebody else's infrastructure rather than a wrong answer. A relative path is refused on the same reasoning, since the only base it could have had is a guess.

What the key bounds is the directory terraform runs in, and that is the whole promise. It does not bound what terraform then reads: a var_file argument is passed through untouched, and which providers, modules and remote state a configuration pulls in is terraform's own resolution. Three keys that read as wider promises were removed rather than left in place. allowed_providers would need the configuration parsed and terraform's provider resolution reproduced. plan_before_apply asserts that an earlier plan saw the same files, which one call cannot know. targeted_destroy_only named a destroy tool that does not exist.

terraform_apply always runs apply -auto-approve -input=false -no-color. No argument and no scope key adds a plan gate in front of it; what gates it is the approval policy for its destructive risk class. The auto_approve argument it used to advertise was ignored and has been removed, for the same reason plan_before_apply was: a brake that is not connected is worse than no brake.

Capability Manifest

The capability manifest is not an operator-edited file. The agent constructs it in-process from its registered tool descriptors and republishes on enrollment and every lease renewal. The router persists it server-side in agents.capability_manifest_json.

The manifest is the allowlist. The router rejects any tool call not present in the published manifest. Editing a file named manifest.json on the EEM host has no effect on the published manifest.

Wire shape (internal reference)

The fields below are the on-wire JSON shape. Operators do not author these; the agent emits them.

FieldPurpose
schema_versionManifest schema revision.
agent_idStable agent identifier assigned at enrollment.
display_nameOperator-visible name; mirrors registration_name.
descriptionShort free-text description supplied by the tool bundle.
shared_limitsObject with max_concurrent_executions_total, max_execution_timeout_ms, max_result_size_bytes.
workspaces[]Workspaces the agent is bound to.
tools[]Flat array; each tool carries name, domain, description, parameters_schema, risk (level, requires_approval, reversible, optional reversal_tool), credential_type, timeout_ms, idempotent, and the workspaces it applies to.

On-Disk Layout

Default layout on a production EEM host:

tree
/etc/erebine/ eem.yaml workspaces.yaml /var/lib/erebine/ agent-signing-key.hex agent-curve-key.hex agent-enrollment.json credentials/ <workspace>/ <credentials_ref> state/ queue.sqlite queue.sqlite-wal queue.sqlite-shm
  • /var/lib/erebine/ must live on a persistent volume. The dispatch queue is not useful if the filesystem is ephemeral.
  • The Curve25519 public half is derived from the private key on load; there is no separate .public file.
  • Logs go to stderr by default. Routing to journald, rsyslog, or a file is the operator's responsibility. See Deploy EEM on a Jumphost for a systemd unit and a compose override.

Further Reading