Document the active Forge deployment loop

338f74ddde46 · AtlantisPleb · · parent d112a5753469

Document the active Forge deployment loop

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified docs/operations/forge-hot-loop.md
  • modified docs/operations/forge-transactional-deployment.md
  • modified docs/operations/release-deployment-fallbacks.md
  • modified docs/runtime-configuration.md

Diff

4 files changed, +61 -42

docs/operations/forge-hot-loop.md modified +43 -28

@@ -2,8 +2,8 @@

2 2
3 3
Date: 2026-08-21
4 4
5
Status: Relup and mirroring are active in production. The automated direct-load
6
loop still requires fleet enablement.
5
Status: Active in production. The Forge loop is the default deployment path
6
for allowlisted code changes. Relup and rolling replacement remain fallbacks.
7 7
8 8
This is the operator procedure for the fast deployment lane: push to the owned
9 9
forge, promote, and watch a code-only change go live across the fleet in

@@ -16,10 +16,10 @@ enablement work.

16 16
| Path | State | Evidence |
17 17
| --- | --- | --- |
18 18
| Direct BEAM transaction on production | Works | `fa4b792` loaded across three nodes via the transaction protocol; `live` target and deployment receipt recorded; uptimes unbroken |
19
| Automated push → promote → build → hot-load loop | Not yet operating | No receipted automated deploy exists, so `/api/status` reports `loop.last_ms: null` and `push_to_live_ms: null` |
19
| Automated push → promote → build → hot-load loop | Active | The web role runs `Builder`, `HotLoader`, and `Janitor`; every fleet node runs the pinned builder sidecar; `/status` reports the lane as **Active** and exposes target, build, deploy, and timing receipts |
20 20
| General relup lane | Active in production | `RelupPackage` binds source and target revisions, release versions, state schemas, target system, and artifact digests before `RelupDeployment` upgrades one node at a time. Production upgraded `0.2.0@81e4c25` to `0.2.1@9763bf7` in 48.838 seconds without restarting the BEAM. |
21 21
| Forge-to-GitHub mirror | Active in production | The production mirror uses a write-enabled deploy key. `MirrorWatch` runs independently of the deploy lane, repairs drift every five minutes, and reports freshness. Forge and GitHub exposed 10 identical refs after the production drill. |
22
| Rolling image replacement | Works, default for structural changes | Current release tooling path |
22
| Rolling image replacement | Available for structural changes | Production requires an operator-directed rollout when the classifier returns `needs_rolling_replace`; staging can use the configured GCP provider |
23 23
24 24
Two consequences worth stating plainly:
25 25

@@ -29,12 +29,14 @@ Two consequences worth stating plainly:

29 29
  The classifier must refuse them for direct loading and route them to a full
30 30
  release path.
31 31
32
## Why the metrics read null
32
## Status reporting
33 33
34
- `loop.last_ms` and the median come from deploy receipts whose result is
35
  `live` with an integer `push_to_live_ms`. The manual `fa4b792`
36
  application produced a receipt without timing, so the projection has no
37
  sample yet. The first automated loop deploy populates both.
34
- `/status` shows whether the Forge lane is **Active** or **Off**, the current
35
  target and stage, the latest build and deployment receipt, the most recent
36
  and median push-to-live times, boot convergence, and mirror freshness.
37
- `loop.last_ms` and the median come from automated `live` receipts with an
38
  integer `push_to_live_ms`. They remain empty until the first automated
39
  direct-load deployment completes.
38 40
- Mirror state `off` means no repository has a configured mirror URL
39 41
  (`OPENAGENTS_FORGE_MIRROR_URLS_JSON` defaults to an empty map). Mirroring
40 42
  feeds GitHub; it plays no part in the deploy loop.

@@ -51,7 +53,9 @@ Every link below exists in code and runs in this order:

51 53
   writes a request to the build queue, and waits. The builder sidecar claims
52 54
   it, fetches the commit into its warm workspace, compiles incrementally,
53 55
   hashes every BEAM, diffs against the baseline manifest, classifies the
54
   changed set, and writes the response plus a digest-addressed artifact.
56
   changed set, and writes the response plus a digest-addressed artifact. The
57
   sidecar uses a fresh source checkout for isolation and a persistent `_build`
58
   and dependency cache for speed. A new pinned builder image seeds that cache.
55 59
4. **Verify**: `Builder` re-verifies digest and manifest, advances the target
56 60
   to `built` with module list and classification, then broadcasts
57 61
   `forge:builds`.

@@ -62,9 +66,9 @@ Every link below exists in code and runs in this order:

62 66
6. **Receipt**: a `live` deployment writes the deploy receipt including
63 67
   `push_to_live_ms`, measured from the push receipt.
64 68
65
## Enablement checklist
69
## Activation state
66 70
67
Work top to bottom; each step gates the next.
71
The production fleet uses this configuration:
68 72
69 73
1. **Publish the builder image.** Build the `forge-builder` Docker target from
70 74
   the same revision as the serving image and push it to Artifact Registry

@@ -88,39 +92,50 @@ Work top to bottom; each step gates the next.

88 92
   whole `OpenAgentsWeb.` layer plus `OpenAgents.Changelog`,
89 93
   `OpenAgents.Forge.Browse`, `OpenAgents.BuildInfo`, and the scratch prefix,
90 94
   with boot-time classification self-tests.
91
6. **Optional — turn the mirror on** by configuring a mirror URL for
95
6. **Optional: Turn the mirror on** by configuring a mirror URL for
92 96
   `openagents.com`. This only affects the public status projection and GitHub
93 97
   mirroring, never deploys.
94 98
95
## First automated drill
99
## Deploy through the Forge loop
96 100
97
Run this once enablement completes; it is also the regression check after any
98
builder change.
101
Use this procedure for every routine deployment. Do not start with an image
102
roll.
99 103
100
1. Pick a commit that changes only allowlisted modules (a template or
101
   LiveView edit is ideal).
102
2. Push it to the forge remote:
104
1. Push the exact commit to the forge remote:
103 105
   `git push openagents <sha>:main`. Confirm the push receipt on
104 106
   `/admin/forge`.
105
3. Promote the new SHA from `/admin/forge`.
106
4. Watch the target walk `promoted → building → built → deploying → live`.
107
5. Assert the deploy receipt shows `result: live`, a nonzero module count, and
107
2. Promote the new SHA from `/admin/forge`.
108
3. Watch the target walk `promoted → building → built → deploying → live` on
109
   `/status` or `/admin/forge`.
110
4. Assert the deploy receipt shows `result: live`, a nonzero module count, and
108 111
   a populated `push_to_live_ms`. Then confirm `/api/status` now reports
109 112
   `forge.loop.last_ms`.
110
6. Restart one node and verify boot convergence restores the same revision
113
5. Restart one node after changing the builder or boot-convergence machinery,
114
   and verify boot convergence restores the same revision
111 115
   before it serves.
112 116
113
Expect the first real build to take minutes: the loop's economics assume the
114
warm workspace that the first build creates. Subsequent web-layer diffs should
115
land in seconds. Receipts measure pipeline time from push ack to live, not
116
human reaction time.
117
The first build after replacing the builder image can take minutes while it
118
seeds the persistent cache. Subsequent web-layer diffs should land in seconds.
119
Receipts measure pipeline time from push acknowledgment to live, not operator
120
reaction time.
121
122
If classification returns `needs_rolling_replace`, keep that receipt and use
123
this fallback order:
124
125
1. Package and deploy a relup when the complete release pair passes appup,
126
   digest, state-schema, and reverse-path validation.
127
2. Use an operator-directed immutable image rollout for configuration,
128
   dependencies, ERTS, native code, migrations, assets, or another structural
129
   change that cannot use a relup.
130
3. Settle the original target with the fallback result so its verified build
131
   becomes the baseline for later direct-load classification.
117 132
118 133
## Failure modes
119 134
120 135
| Symptom | Meaning | Action |
121 136
| --- | --- | --- |
122 137
| Target stalls at `building`, fails `build_timeout` | No sidecar claimed the request | Check the builder container is running and the queue volume is shared |
123
| `needs_rolling_replace` with `structural_reasons` | Honest refusal: config, dependencies, assets, or release files changed | Ride the image-roll path; this is correct behavior |
138
| `needs_rolling_replace` with `structural_reasons` | Honest refusal: config, dependencies, assets, or release files changed | Try the packaged relup path; use an operator-directed image rollout when the release is incompatible |
124 139
| `needs_rolling_replace` with `off_allowlist:` reasons | The diff touched modules outside the allowlist | Widen deliberately in config, or route around the change |
125 140
| Artifact verification failure | Digest or manifest mismatch between builder and coordinator | Treat as a builder defect; inspect the retained build output |
126 141
| A node restarts mid-fleet-deploy | Membership recheck pauses phases | Boot convergence holds the node out until it converges |
docs/operations/forge-transactional-deployment.md modified +5 -4

@@ -159,10 +159,11 @@ convergence interval.

159 159
| `OPENAGENTS_FORGE_BOOT_RETRY_MIN_MS` | Initial convergence retry interval |
160 160
| `OPENAGENTS_FORGE_BOOT_RETRY_MAX_MS` | Maximum convergence retry interval |
161 161
162
Keep `OPENAGENTS_FEATURE_FORGE_DEPLOY=false` and
163
`OPENAGENTS_FEATURE_BOOT_CONVERGENCE=false` until the isolated distributed
164
staging lane exists and Gate 11 adds the relup and rolling-replacement
165
fallbacks.
162
Production enables `OPENAGENTS_FEATURE_FORGE_DEPLOY` and
163
`OPENAGENTS_FEATURE_BOOT_CONVERGENCE` after pinning the serving and builder
164
images. Staging must also configure its isolated rolling provider before it
165
enables the complete automated fallback lane. `/status` reports the active
166
state, current stage, timing receipts, boot convergence, and mirror freshness.
166 167
167 168
## Verification
168 169
docs/operations/release-deployment-fallbacks.md modified +11 -8

@@ -2,13 +2,15 @@

2 2
3 3
Date: 2026-08-20
4 4
5
Status: Implemented. Production uses the relup lane for compatible application
6
releases and the coordinated rolling lane for structural changes.
5
Status: Implemented. Production uses the Forge direct-load loop first, a relup
6
for compatible full releases, and an operator-directed rolling replacement for
7
remaining structural changes.
7 8
8 9
## Purpose
9 10
10
Use this runbook when a candidate cannot use the direct BEAM transaction. The
11
classifier must choose one strategy for the complete candidate:
11
Use this runbook only after the Forge classifier refuses a direct BEAM
12
transaction. The classifier must choose one strategy for the complete
13
candidate:
12 14
13 15
- Use a relup for a packaged `X.Y.Z` transition when the generated appup,
14 16
  target system, exact revisions, artifact digests, state schemas, and reverse

@@ -170,10 +172,11 @@ The script records the content-addressed `sha256:` image ID under

170 172
`.git/openagents/images/<full-sha>.json`. A mutable tag is a convenience label,
171 173
not deployment identity. Pass only the digest to the replacement provider.
172 174
173
`OpenAgents.Forge.RollingReplacement` owns the provider-neutral rollout. Gate
174
12 must implement `OpenAgents.Forge.RollingProvider` for the isolated staging
175
infrastructure. Keep machine inventory, credentials, addresses, and provider
176
resource names outside the repository.
175
`OpenAgents.Forge.RollingReplacement` owns the provider-neutral rollout.
176
Staging uses the GCP provider for automated replacement. Production permits no
177
implicit provider: an operator must execute and verify the immutable image
178
rollout, then settle the retained Forge target. Keep machine inventory,
179
credentials, addresses, and provider resource names outside the repository.
177 180
178 181
The provider reports its exact connected infrastructure inventory through
179 182
`members/0`. A hidden controller must not include itself or a temporary RPC
docs/runtime-configuration.md modified +2 -2

@@ -207,7 +207,7 @@ discovery, node identity, cookie, and bounded distribution ports.

207 207
| --- | --- |
208 208
| `OPENAGENTS_FORGE_REPOSITORIES` | Exactly `openagents.com` |
209 209
| `OPENAGENTS_FORGE_OWNER` | Exactly `OpenAgentsInc` |
210
| `OPENAGENTS_FORGE_INTERNAL_GIT_URL` | HTTP(S) service URL with no embedded credentials |
210
| `OPENAGENTS_FORGE_INTERNAL_GIT_URL` | Credential-free HTTP(S) owner root, such as `http://127.0.0.1:8080/OpenAgentsInc`; do not include the retired `/git` prefix |
211 211
| `OPENAGENTS_FORGE_OPERATOR_TOKEN` | Secret required when the forge is enabled; empty while disabled |
212 212
| `OPENAGENTS_FORGE_MIRROR_URLS_JSON` | Optional JSON object from repository name to credential-free git mirror URL; empty disables one-way GitHub mirroring |
213 213
| `OPENAGENTS_FORGE_BUILD_EXECUTOR` | `sidecar` |

@@ -226,7 +226,7 @@ discovery, node identity, cookie, and bounded distribution ports.

226 226
| `OPENAGENTS_FORGE_DEPLOY_TOKEN_TTL_MS` | `120000`; admitted range 30 seconds to 30 minutes and at least eight deployment timeouts |
227 227
| `OPENAGENTS_FORGE_BOOT_RETRY_MIN_MS` | `1000`; admitted range 100 milliseconds to one minute |
228 228
| `OPENAGENTS_FORGE_BOOT_RETRY_MAX_MS` | `30000`; admitted range one to 300 seconds and not less than the minimum |
229
| `OPENAGENTS_FORGE_ROLLING_PROVIDER` | `gcp` when fleet deployment is enabled; empty while disabled |
229
| `OPENAGENTS_FORGE_ROLLING_PROVIDER` | `gcp` for the automated staging fallback; production leaves it empty and uses an explicit operator-directed rollout when direct loading and relup cannot apply the candidate |
230 230
| `OPENAGENTS_GCP_ROLLING_PROJECT_ID` | Isolated staging project; must differ from `OPENAGENTS_PRODUCTION_PROJECT_ID` |
231 231
| `OPENAGENTS_PRODUCTION_PROJECT_ID` | Production project used only as a fail-closed comparison value |
232 232
| `OPENAGENTS_GCP_ROLLING_ZONE` | Zone that contains the three stable staging instances |

This page updates live while a promote is in flight · changelog