Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
237 lines (234 loc) · 15.4 KB
/
Copy pathdocker-compose.yml
File metadata and controls
237 lines (234 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# Substrate launch manifest — the one declaration of how the image runs.
#
# Setup commands live in one place: README § Installation. This header names the
# inputs; it does not restate the sequence.
#
# Install inputs, read from the .env beside this file or from the shell
# environment (the shell wins over .env). No client config, credential cache or
# other container's settings is ever read:
# SUBSTRATE_NAME default `substrate` -> container substrate-live,
# volumes substrate-workspace + substrate-surreal
# SUBSTRATE_PORT_PREFIX default `18` -> host port = prefix followed by the
# last three digits of the container port (8080 -> 18080)
# DISCOVERY_ENDPOINT + METABOB_API_KEY join a hub (the image derives the role)
# one provider key (ANTHROPIC_API_KEY, OPENAI_API_KEY, …)
# PROFILE standalone | hub | hub-minimal | spoke | surface | compute
# PUBLIC_IP the address a hub advertises to its spokes
# SUBSTRATE_GIT_PAT + SUBSTRATE_REPO_OWNER push capability and its scope
# SUBSTRATE_UPDATE_CHANNEL canary | fleet | hold (default canary): which revision
# this node runs; fleet and hold nodes consume and do not land
# SUBSTRATE_ACCEPTANCE 1 on a throwaway acceptance install only: lets its gap-store
# holder take the hold channel, so the run judges the image it names
# Everything else below is advanced configuration, forwarded unchanged.
#
# Every derivation (role, endpoints, key requirement, alias checks) runs in the
# image. This file only interpolates names and ports, and forwards inputs. Compose
# cannot warn or refuse, so the deprecated names are forwarded as supplied and the
# image's configuration funnel (gen-env) judges them: it warns on each, and
# refuses a conflicting set, or a partial one on a new volume, before anything is
# written.
#
# Deprecated exact-name aliases, still honoured so an existing fleet is adopted
# with the same container, volumes and ports:
# SUBSTRATE_CONTAINER, WORKSPACE_VOLUME, SURREAL_VOLUME -> SUBSTRATE_NAME
# ACTIVITY_API_PORT, DEV_VESSEL_PORT, DISCOVERY_PORT, IDENTITY_PORT,
# GOAL_HOST_PORT, CONCEPT_DB_PORT, STATEFUL_UI_PORT,
# HUMAN_SURFACE_PORT -> SUBSTRATE_PORT_PREFIX
# An alias outranks the name that replaces it (it is the nested default's outer
# term), so an old .env resolves exactly as it did before the new names existed.
#
# The image is a fat single container: surrealdb + valkey + every vessel run as
# systemd units inside it (hence privileged + tmpfs /run). Both named volumes
# persist state — /workspace (learning state, memoryNotes, secrets) and
# /var/lib/surrealdb (the datastore); dropping either loses learned state.
# No top-level `name:`: the compose project stays the directory's (or
# COMPOSE_PROJECT_NAME), so a fleet created from an earlier copy of this file is
# recreated in place rather than colliding with itself under a new project name.
# Keep one directory per fleet.
services:
substrate:
image: ${SUBSTRATE_IMAGE:-ghcr.io/avigopal/substrate:dev}
container_name: ${SUBSTRATE_CONTAINER:-${SUBSTRATE_NAME:-substrate}-live}
privileged: true # systemd as PID 1 inside the container
stop_signal: SIGRTMIN+3 # systemd graceful shutdown
# SHUTDOWN IS A DRAIN, NOT A KILL. goal-host and development-vessel drain for
# up to 240s (GOAL_HOST_DRAIN_MS / VESSEL_DRAIN_MS), and surrealdb.service
# inherits systemd's 90s default stop timeout to flush RocksDB. The grace
# period covers both plus the shutdown of the remaining units; anything
# shorter SIGKILLs the datastore mid-flush on the volume that holds every
# trace and posterior. `docker compose stop` reads this value, so the engine's
# own stop is the correct stop.
stop_grace_period: 360s
restart: unless-stopped
# No healthcheck here: the image's HEALTHCHECK applies. A second definition
# in this file is the drift the manifest exists to remove.
# Marks a container as created from this manifest, so a wrapper can tell it
# from one a plain `docker run` or another compose file created under the same
# name — and never recreate the latter by accident.
labels:
substrate.launch-manifest: "1"
environment:
# ── Install identity, forwarded so the image can judge it ────────────────
# Raw values (empty when unset): the image distinguishes "supplied" from
# "defaulted", which is what its alias warnings and refusals need, and it
# can only compute its own published address (substrate-connect,
# /bootstrap) from port inputs it can see.
SUBSTRATE_NAME: ${SUBSTRATE_NAME:-}
SUBSTRATE_PORT_PREFIX: ${SUBSTRATE_PORT_PREFIX:-}
SUBSTRATE_CONTAINER: ${SUBSTRATE_CONTAINER:-}
WORKSPACE_VOLUME: ${WORKSPACE_VOLUME:-}
SURREAL_VOLUME: ${SURREAL_VOLUME:-}
RELAY_PORT: ${RELAY_PORT:-}
# The port the relay ANNOUNCES is the host port this file publishes it on
# (the relay mapping below), so the address /bootstrap hands a spoke is one
# something actually listens on. Set RELAY_ANNOUNCE_PORT only when peers
# dial a different port than the published one (e.g. the container port,
# across a shared container network).
RELAY_ANNOUNCE_PORT: ${RELAY_ANNOUNCE_PORT:-${RELAY_PORT:-${SUBSTRATE_PORT_PREFIX:-18}333}}
ACTIVITY_API_PORT: ${ACTIVITY_API_PORT:-}
DEV_VESSEL_PORT: ${DEV_VESSEL_PORT:-}
DISCOVERY_PORT: ${DISCOVERY_PORT:-}
IDENTITY_PORT: ${IDENTITY_PORT:-}
GOAL_HOST_PORT: ${GOAL_HOST_PORT:-}
CONCEPT_DB_PORT: ${CONCEPT_DB_PORT:-}
STATEFUL_UI_PORT: ${STATEFUL_UI_PORT:-}
HUMAN_SURFACE_PORT: ${HUMAN_SURFACE_PORT:-}
# ── Provider keys ────────────────────────────────────────────────────────
# NOT `:?`. A spoke needs no provider key, and compose cannot see the role;
# the image enforces the requirement at boot, where the role is known.
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-}
LLM_DEFAULT_MODEL: ${LLM_DEFAULT_MODEL:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
CHUTES_API_KEY: ${CHUTES_API_KEY:-}
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
GOOGLE_API_KEY: ${GOOGLE_API_KEY:-}
# Self-hosted vLLM. Single instance: VLLM_BASE_URL + VLLM_MODELS
# (comma-separated served ids), VLLM_API_KEY only if the server runs with
# --api-key. Multiple: VLLM_ENDPOINTS='[{"id":..,"baseURL":..,"models":[..]}]'.
VLLM_BASE_URL: ${VLLM_BASE_URL:-}
VLLM_MODELS: ${VLLM_MODELS:-}
VLLM_API_KEY: ${VLLM_API_KEY:-}
VLLM_ENDPOINTS: ${VLLM_ENDPOINTS:-}
# RunPod Serverless (queue endpoint, OpenAI-compatible route). The resolver
# registers the arm only when RUNPOD_ENDPOINT_ID is set, and offers it only
# while a worker is warm.
RUNPOD_ENDPOINT_ID: ${RUNPOD_ENDPOINT_ID:-}
RUNPOD_API_KEY: ${RUNPOD_API_KEY:-}
RUNPOD_MODELS: ${RUNPOD_MODELS:-}
RUNPOD_COST_PER_MTOK: ${RUNPOD_COST_PER_MTOK:-}
# Replaces the whole LLM arm set without editing a tracked file. Read before
# systemd starts, so it needs no gen-env emission.
LLM_ARMS: ${LLM_ARMS:-}
# ── Join / federation ────────────────────────────────────────────────────
# DISCOVERY_ENDPOINT + METABOB_API_KEY are the whole join; the rest are
# overrides of what the image derives from them. SUBSTRATE_API_KEY and
# SUBSTRATE_ENDPOINT are the new names for the key and endpoint: gen-env
# accepts either, and the new one wins a conflict (retire-metabob-names).
DISCOVERY_ENDPOINT: ${DISCOVERY_ENDPOINT:-}
METABOB_API_KEY: ${METABOB_API_KEY:-}
SUBSTRATE_API_KEY: ${SUBSTRATE_API_KEY:-}
HUB_DISCOVERY_URL: ${HUB_DISCOVERY_URL:-}
ACTIVITY_API_ENDPOINT: ${ACTIVITY_API_ENDPOINT:-}
METABOB_ENDPOINT: ${METABOB_ENDPOINT:-}
SUBSTRATE_ENDPOINT: ${SUBSTRATE_ENDPOINT:-}
IDENTITY_VESSEL_URL: ${IDENTITY_VESSEL_URL:-}
IDENTITY_ENDPOINT: ${IDENTITY_ENDPOINT:-}
FED_SUBSTRATE_ID: ${FED_SUBSTRATE_ID:-}
FED_EXTRA_SHAPE: ${FED_EXTRA_SHAPE:-}
RELAY_MULTIADDR: ${RELAY_MULTIADDR:-}
PEER_MULTIADDR: ${PEER_MULTIADDR:-}
PEER_DISCOVERY_ENDPOINTS: ${PEER_DISCOVERY_ENDPOINTS:-}
MAX_PEER_DEPTH: ${MAX_PEER_DEPTH:-}
FEDERATION_PEER_AUTH_MODE: ${FEDERATION_PEER_AUTH_MODE:-}
FEDERATION_SIGNING_SECRET: ${FEDERATION_SIGNING_SECRET:-}
# Makes discovery's /bootstrap advertise a reachable address instead of
# loopback. The *_PUBLIC_URL / *_PUBLIC_PORT overrides are inert without it.
PUBLIC_IP: ${PUBLIC_IP:-}
FED_PUBLIC_IP: ${FED_PUBLIC_IP:-}
DISCOVERY_PUBLIC_URL: ${DISCOVERY_PUBLIC_URL:-}
IDENTITY_PUBLIC_URL: ${IDENTITY_PUBLIC_URL:-}
ACTIVITY_PUBLIC_URL: ${ACTIVITY_PUBLIC_URL:-}
DISCOVERY_PUBLIC_PORT: ${DISCOVERY_PUBLIC_PORT:-}
IDENTITY_PUBLIC_PORT: ${IDENTITY_PUBLIC_PORT:-}
ACTIVITY_PUBLIC_PORT: ${ACTIVITY_PUBLIC_PORT:-}
SUBSTRATE_BIND_HOST: ${SUBSTRATE_BIND_HOST:-}
# ── Vessel selection ─────────────────────────────────────────────────────
# Precedence (highest first): PROFILE > ENABLED_VESSELS > ENABLED_ROLES,
# then + ENABLED_EXTRA_VESSELS, then - DISABLED_VESSELS.
PROFILE: ${PROFILE:-}
SUBSTRATE_UPDATE_CHANNEL: ${SUBSTRATE_UPDATE_CHANNEL:-}
SUBSTRATE_ACCEPTANCE: ${SUBSTRATE_ACCEPTANCE:-}
ENABLED_VESSELS: ${ENABLED_VESSELS:-}
ENABLED_ROLES: ${ENABLED_ROLES:-}
ENABLED_EXTRA_VESSELS: ${ENABLED_EXTRA_VESSELS:-}
# Which repos this node lands; gen-env carries it to setup-git-push (ownership = push clones).
SUBSTRATE_PUSH_VESSELS: ${SUBSTRATE_PUSH_VESSELS:-}
# Resolve URL of the node that holds the gap store; empty on the holder itself.
GAP_STORE_ENDPOINT: ${GAP_STORE_ENDPOINT:-}
DISABLED_VESSELS: ${DISABLED_VESSELS:-}
# ── Push capability and autonomy ─────────────────────────────────────────
# SUBSTRATE_GIT_PAT grants push capability; SUBSTRATE_REPO_OWNER scopes it.
SUBSTRATE_GIT_PAT: ${SUBSTRATE_GIT_PAT:-}
SUBSTRATE_REPO_OWNER: ${SUBSTRATE_REPO_OWNER:-}
GITHUB_TOKEN: ${GITHUB_TOKEN:-}
# Emergency stop for autonomous landing: 0 = no commit, no push, no host-sync
# intent; unset or 1 = landings proceed as the capability and pushPolicy allow.
MITOSIS_DIRECT_PUSH: ${MITOSIS_DIRECT_PUSH:-}
ROUTE_EDIT_INTENT_TO_COMPOSE: ${ROUTE_EDIT_INTENT_TO_COMPOSE:-}
# ── Stores and roots ─────────────────────────────────────────────────────
SUBSTRATE_ROOT: ${SUBSTRATE_ROOT:-}
SURREALDB_URL: ${SURREALDB_URL:-}
REDIS_URL: ${REDIS_URL:-}
# ── API key signing secret ───────────────────────────────────────────────
# The HMAC secret identity-vessel signs and verifies every mb- key with. The
# image fails closed without it on an existing datastore and names it in the
# error; the escape hatch below keeps a legacy default (keys stay forgeable).
API_KEY_SECRET: ${API_KEY_SECRET:-}
API_KEY_SECRET_PREVIOUS: ${API_KEY_SECRET_PREVIOUS:-}
ALLOW_INSECURE_API_KEY_SECRET: ${ALLOW_INSECURE_API_KEY_SECRET:-}
# One port set on every profile; a vessel the profile does not run simply does
# not answer. Each legacy *_PORT, when set, wins over the prefix.
#
# Exposure is decided here and only here, by the host address each port is
# published on (advanced; unset = every host interface, as always):
# SUBSTRATE_PUBLISH_IP=127.0.0.1 every port on the local host only
# <VESSEL>_PUBLISH_IP=<address> one port, outranking the above; <VESSEL>
# is the prefix of that port's *_PORT name
# (HUMAN_SURFACE_PUBLISH_IP, RELAY_PUBLISH_IP, …)
# A compose override file cannot narrow a mapping: compose MERGES `ports`
# lists, so an override's 127.0.0.1 mapping is added beside the public one
# rather than replacing it. These variables are the supported way.
ports:
- "${ACTIVITY_API_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${ACTIVITY_API_PUBLISH_IP:+:}${ACTIVITY_API_PORT:-${SUBSTRATE_PORT_PREFIX:-18}080}:8080" # activity-api
- "${DEV_VESSEL_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${DEV_VESSEL_PUBLISH_IP:+:}${DEV_VESSEL_PORT:-${SUBSTRATE_PORT_PREFIX:-18}090}:8090" # development-vessel
- "${DISCOVERY_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${DISCOVERY_PUBLISH_IP:+:}${DISCOVERY_PORT:-${SUBSTRATE_PORT_PREFIX:-18}100}:8100" # discovery-vessel
- "${IDENTITY_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${IDENTITY_PUBLISH_IP:+:}${IDENTITY_PORT:-${SUBSTRATE_PORT_PREFIX:-18}101}:8101" # identity-vessel
- "${GOAL_HOST_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${GOAL_HOST_PUBLISH_IP:+:}${GOAL_HOST_PORT:-${SUBSTRATE_PORT_PREFIX:-18}210}:8210" # goal-host-vessel
- "${CONCEPT_DB_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${CONCEPT_DB_PUBLISH_IP:+:}${CONCEPT_DB_PORT:-${SUBSTRATE_PORT_PREFIX:-18}260}:8260" # concept-db
- "${STATEFUL_UI_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${STATEFUL_UI_PUBLISH_IP:+:}${STATEFUL_UI_PORT:-${SUBSTRATE_PORT_PREFIX:-18}270}:8270" # stateful-ui-vessel
- "${HUMAN_SURFACE_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${HUMAN_SURFACE_PUBLISH_IP:+:}${HUMAN_SURFACE_PORT:-${SUBSTRATE_PORT_PREFIX:-18}310}:8310" # human-surface-vessel
# federation relay (libp2p), run in-container by the hub profiles.
# RELAY_PORT keeps an existing hub on the host port its spokes already dial.
- "${RELAY_PUBLISH_IP:-${SUBSTRATE_PUBLISH_IP:+${SUBSTRATE_PUBLISH_IP}:}}${RELAY_PUBLISH_IP:+:}${RELAY_PORT:-${SUBSTRATE_PORT_PREFIX:-18}333}:30333"
volumes:
- substrate-workspace:/workspace # named volume — portable, not a host path
- substrate-surreal:/var/lib/surrealdb # the datastore — dropping it loses all learning state
tmpfs:
- /run
- /run/lock
volumes:
# Explicitly NAMED, so compose attaches to the same volumes every launch uses
# instead of inventing project-prefixed ones. Without `name:`, compose prefixes
# volume names with the project, and `up` against a fleet started any other way
# creates EMPTY volumes and starts a healthy-looking container with no learned
# state, orphaning the real ones — silently.
#
# The defaults reproduce the historical names, so an existing install is
# adopted as-is; SUBSTRATE_NAME=<name> gives <name>-workspace / <name>-surreal.
substrate-workspace:
name: ${WORKSPACE_VOLUME:-${SUBSTRATE_NAME:-substrate}-workspace}
substrate-surreal:
name: ${SURREAL_VOLUME:-${SUBSTRATE_NAME:-substrate}-surreal}