@delorenj/pjangler 1.1.5 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.mise/scripts/link-agentfiles.sh +9 -0
  2. package/dist/index.js +1492 -358
  3. package/dist/mcp-server.js +2391 -290
  4. package/package.json +5 -2
  5. package/templates/commonproject/.mise/scripts/create-plane-project.sh +26 -0
  6. package/templates/commonproject/copier.yml +2 -0
  7. package/templates/commonproject/template/.agents/hooks/README.md +151 -0
  8. package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +63 -0
  9. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-journal-write.sh +327 -0
  10. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-recall.sh +180 -0
  11. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-retain.sh +74 -0
  12. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-session-end.sh +57 -0
  13. package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-bank.sh +57 -0
  14. package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-journal.sh +48 -0
  15. package/templates/commonproject/template/.agents/hooks/hooks.master.json +97 -0
  16. package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +30 -0
  17. package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +51 -0
  18. package/templates/commonproject/template/.agents/hooks/reminder-for-skill-check.sh +20 -0
  19. package/templates/commonproject/template/.agents/hooks/sync.py +618 -0
  20. package/templates/commonproject/template/.agents/local.example.json +15 -0
  21. package/templates/commonproject/template/.mise/scripts/hindsight-setup.sh +62 -0
  22. package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +110 -0
  23. package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +45 -0
  24. package/templates/commonproject/template/mise.toml.jinja +42 -0
  25. package/templates/hermes-agent/.codegraph/daemon.pid +6 -0
  26. package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +10 -0
  27. package/templates/hermes-agent/README.md +16 -14
  28. package/templates/hermes-agent/copier.yml +7 -25
  29. package/templates/hermes-agent/docs/architecture.md +16 -18
  30. package/templates/hermes-agent/docs/fleet-control-plane/README.md +35 -0
  31. package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +253 -0
  32. package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +280 -0
  33. package/templates/hermes-agent/docs/fleet-control-plane/implementation-readiness-report-2026-06-27.md +118 -0
  34. package/templates/hermes-agent/docs/fleet-control-plane/prd.md +141 -0
  35. package/templates/hermes-agent/docs/operations.md +17 -19
  36. package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +17 -13
  37. package/templates/hermes-agent/docs/{scrum-master → sentinel}/README.md +47 -46
  38. package/templates/hermes-agent/docs/{scrum-master → sentinel}/architecture.md +39 -38
  39. package/templates/hermes-agent/docs/{scrum-master → sentinel}/development.md +63 -99
  40. package/templates/hermes-agent/docs/{scrum-master → sentinel}/providers.md +2 -2
  41. package/templates/hermes-agent/docs/sentinel.md +18 -0
  42. package/templates/hermes-agent/install-local.sh +22 -20
  43. package/templates/hermes-agent/runtime-scaffold/README.md +10 -9
  44. package/templates/hermes-agent/scripts/backfill-fleet-sot.sh +12 -21
  45. package/templates/hermes-agent/scripts/fleet-sync.sh +369 -0
  46. package/templates/hermes-agent/scripts/migrate-unify.sh +252 -0
  47. package/templates/hermes-agent/scripts/unify-pm.sh +148 -0
  48. package/templates/hermes-agent/template/.runtime-scaffold/README.md +10 -9
  49. package/templates/hermes-agent/template/.scripts/01-config.sh +2 -3
  50. package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -2
  51. package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +47 -19
  52. package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +71 -27
  53. package/templates/hermes-agent/template/.scripts/30-telegram.sh +3 -3
  54. package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +3 -3
  55. package/templates/hermes-agent/template/.scripts/70-systemd.sh +48 -43
  56. package/templates/hermes-agent/template/.scripts/80-registry.sh +2 -2
  57. package/templates/hermes-agent/template/.scripts/99-summary.sh +3 -4
  58. package/templates/hermes-agent/template/.scripts/_lib.sh +4 -19
  59. package/templates/hermes-agent/template/.scripts/checkpoint.sh +36 -0
  60. package/templates/hermes-agent/template/.scripts/config.example.toml +11 -5
  61. package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.sh → heartbeat.sh} +84 -31
  62. package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +1 -1
  63. package/templates/hermes-agent/template/.scripts/providers/plane.sh +9 -3
  64. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/emit-event.py +5 -5
  65. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-autonomous-review.sh +6 -6
  66. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-close-gate.sh +2 -2
  67. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/autonomous-delegated-review.md +7 -7
  68. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/bloodbank-events.md +3 -3
  69. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/continuous-ticket-orchestration.md +6 -6
  70. package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.prompt.md.jinja → sentinel.prompt.md.jinja} +5 -4
  71. package/templates/hermes-agent/template/SOUL.md.jinja +3 -6
  72. package/templates/hermes-agent/template/hermes.jinja +6 -15
  73. package/templates/hermes-agent/template/role.yaml.jinja +9 -8
  74. package/templates/commonproject/.claude/commands/bmad/bmb/agents/agent-builder.md +0 -14
  75. package/templates/commonproject/.claude/commands/bmad/bmb/agents/module-builder.md +0 -14
  76. package/templates/commonproject/.claude/commands/bmad/bmb/agents/workflow-builder.md +0 -14
  77. package/templates/commonproject/.claude/commands/bmad/bmb/workflows/agent.md +0 -5
  78. package/templates/commonproject/.claude/commands/bmad/bmb/workflows/module.md +0 -5
  79. package/templates/commonproject/.claude/commands/bmad/bmb/workflows/workflow.md +0 -5
  80. package/templates/commonproject/.claude/commands/bmad/bmm/agents/analyst.md +0 -14
  81. package/templates/commonproject/.claude/commands/bmad/bmm/agents/architect.md +0 -14
  82. package/templates/commonproject/.claude/commands/bmad/bmm/agents/dev.md +0 -14
  83. package/templates/commonproject/.claude/commands/bmad/bmm/agents/pm.md +0 -14
  84. package/templates/commonproject/.claude/commands/bmad/bmm/agents/quick-flow-solo-dev.md +0 -14
  85. package/templates/commonproject/.claude/commands/bmad/bmm/agents/sm.md +0 -14
  86. package/templates/commonproject/.claude/commands/bmad/bmm/agents/tea.md +0 -14
  87. package/templates/commonproject/.claude/commands/bmad/bmm/agents/tech-writer.md +0 -14
  88. package/templates/commonproject/.claude/commands/bmad/bmm/agents/ux-designer.md +0 -14
  89. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/check-implementation-readiness.md +0 -5
  90. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/code-review.md +0 -13
  91. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/correct-course.md +0 -13
  92. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-architecture.md +0 -5
  93. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-epics-and-stories.md +0 -5
  94. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-dataflow.md +0 -13
  95. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-diagram.md +0 -13
  96. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-flowchart.md +0 -13
  97. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-wireframe.md +0 -13
  98. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-product-brief.md +0 -5
  99. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-story.md +0 -13
  100. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-ux-design.md +0 -5
  101. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/dev-story.md +0 -13
  102. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/document-project.md +0 -13
  103. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/generate-project-context.md +0 -5
  104. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/prd.md +0 -5
  105. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-dev.md +0 -5
  106. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-spec.md +0 -5
  107. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/research.md +0 -5
  108. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/retrospective.md +0 -13
  109. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-planning.md +0 -13
  110. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-status.md +0 -13
  111. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-atdd.md +0 -13
  112. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-automate.md +0 -13
  113. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-ci.md +0 -13
  114. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-framework.md +0 -13
  115. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-nfr.md +0 -13
  116. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-design.md +0 -13
  117. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-review.md +0 -13
  118. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-trace.md +0 -13
  119. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-init.md +0 -13
  120. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-status.md +0 -13
  121. package/templates/commonproject/.claude/commands/bmad/cis/agents/brainstorming-coach.md +0 -14
  122. package/templates/commonproject/.claude/commands/bmad/cis/agents/creative-problem-solver.md +0 -14
  123. package/templates/commonproject/.claude/commands/bmad/cis/agents/design-thinking-coach.md +0 -14
  124. package/templates/commonproject/.claude/commands/bmad/cis/agents/innovation-strategist.md +0 -14
  125. package/templates/commonproject/.claude/commands/bmad/cis/agents/presentation-master.md +0 -14
  126. package/templates/commonproject/.claude/commands/bmad/cis/agents/storyteller.md +0 -14
  127. package/templates/commonproject/.claude/commands/bmad/cis/workflows/design-thinking.md +0 -13
  128. package/templates/commonproject/.claude/commands/bmad/cis/workflows/innovation-strategy.md +0 -13
  129. package/templates/commonproject/.claude/commands/bmad/cis/workflows/problem-solving.md +0 -13
  130. package/templates/commonproject/.claude/commands/bmad/cis/workflows/storytelling.md +0 -13
  131. package/templates/commonproject/.claude/commands/bmad/core/agents/bmad-master.md +0 -14
  132. package/templates/commonproject/.claude/commands/bmad/core/tasks/index-docs.md +0 -9
  133. package/templates/commonproject/.claude/commands/bmad/core/tasks/shard-doc.md +0 -9
  134. package/templates/commonproject/.claude/commands/bmad/core/workflows/brainstorming.md +0 -5
  135. package/templates/commonproject/.claude/commands/bmad/core/workflows/party-mode.md +0 -5
  136. package/templates/commonproject/.claude/commands/bmad/custom/workflows/ticket-lifecycle.md +0 -5
  137. package/templates/hermes-agent/docs/scrum-master-sentinel.md +0 -17
  138. package/templates/hermes-agent/template/.scripts/75-scrum-master.sh +0 -117
  139. package/templates/hermes-agent/template/.scripts/90-chain-scrum-master.sh +0 -45
@@ -0,0 +1,141 @@
1
+ ---
2
+ stepsCompleted: [1]
3
+ inputDocuments:
4
+ - docs/architecture.md
5
+ - docs/operations.md
6
+ - README.md
7
+ - /home/delorenj/code/pjangler/src/commands/hermes/RunCopierTemplate.ts
8
+ - /home/delorenj/code/pjangler/src/parity/index.ts
9
+ - /home/delorenj/.hermes/agents-registry.yaml
10
+ workflowType: prd
11
+ project_name: Fleet Control Plane
12
+ date: 2026-06-27
13
+ status: draft-ready
14
+ ---
15
+
16
+ # Fleet Control Plane PRD
17
+
18
+ ## Problem
19
+
20
+ The Fleet is composed of valuable independent repos and runtime surfaces, but
21
+ the binding contract between them is implicit and drifting.
22
+
23
+ Current symptoms include:
24
+
25
+ - `pjangler` is already the de facto control plane, but its role is not stated
26
+ as an architectural contract.
27
+ - `pjangler/templates/hermes-agent` can carry dirty vendored template changes
28
+ that differ from `~/code/hermes-agent-template`.
29
+ - The live fleet uses checkpoint timers while newer dirty template work points
30
+ toward heartbeat timers.
31
+ - The registry, profiles, fleet env, systemd units, and role manifests can
32
+ disagree without one authoritative reconciliation path.
33
+ - n8n is a promising visual orchestration layer, but should not become another
34
+ competing source of truth.
35
+
36
+ ## Product Goal
37
+
38
+ Make `pjangler` the explicit Fleet control plane and introduce a versioned Fleet
39
+ contract that lets the independent parts stay independent while making their
40
+ runtime coupling visible, validated, and repairable.
41
+
42
+ ## Users
43
+
44
+ - Primary operator: Deloren, managing a personal but production-like fleet.
45
+ - Agent implementers: Codex, Hermes PM agents, and future worker agents acting
46
+ from explicit contracts.
47
+ - Visual orchestrator: n8n workflows that coordinate Fleet health, delegation,
48
+ and reconciliation.
49
+
50
+ ## Functional Requirements
51
+
52
+ FR1: The system shall define a versioned Fleet contract that records template
53
+ source versions, service model, schema versions, and supported runtime modes.
54
+
55
+ FR2: `pjangler` shall expose `pj templates status` to report each vendored
56
+ template source, commit, tag, dirty state, and effective provisioning source.
57
+
58
+ FR3: `pjangler` shall expose `pj fleet status` to compare the Fleet contract
59
+ against `~/.hermes/fleet.env`, `~/.hermes/agents-registry.yaml`, profiles,
60
+ role manifests, runtime repos, and systemd units.
61
+
62
+ FR4: `pjangler` shall expose `pj fleet reconcile` with dry-run by default and
63
+ explicit `--apply` mutation mode.
64
+
65
+ FR5: The Fleet registry shall support managed exceptions through fields such as
66
+ `managed`, `kind`, `owner`, `source`, and `notes`.
67
+
68
+ FR6: The implementation shall validate `.project.json`, `role.yaml`,
69
+ `agents-registry.yaml`, `profile.yaml`, and `fleet-contract.yaml` with explicit
70
+ schemas.
71
+
72
+ FR7: `pjangler` publishing shall fail when vendored template submodules are
73
+ dirty, mismatched, or missing required files.
74
+
75
+ FR8: The heartbeat v2 strategy shall use n8n as the visual orchestration plane
76
+ and systemd as the local survival fallback.
77
+
78
+ FR9: n8n workflows shall read Fleet state from `pjangler`/Fleet registry rather
79
+ than storing independent Fleet truth.
80
+
81
+ FR10: n8n workflow generation shall be reproducible from `pjangler` and must be
82
+ validatable before creation or update.
83
+
84
+ FR11: The implementation shall repair known current drift, including stale
85
+ runtime scaffold fallback paths, heartbeat/checkpoint mismatch, stale registry
86
+ Hermes paths, and invalid generated role model fields.
87
+
88
+ FR12: The operator shall be able to inspect and run all checks locally without
89
+ network side effects unless an apply or create flag is explicitly provided.
90
+
91
+ ## Non-Functional Requirements
92
+
93
+ NFR1: Default commands must be read-only or dry-run.
94
+
95
+ NFR2: Reconciliation must never destroy or merge runtime state automatically
96
+ when profiles or runtime repos contain ambiguous data.
97
+
98
+ NFR3: n8n outage must not prevent baseline agent health, gateway operation,
99
+ consumer operation, or runtime checkpoint fallback.
100
+
101
+ NFR4: Validation output must be deterministic and grep-friendly.
102
+
103
+ NFR5: Release gates must be suitable for local CLI use and npm `prepublishOnly`.
104
+
105
+ NFR6: Secrets must remain outside committed artifacts and generated workflow
106
+ definitions.
107
+
108
+ NFR7: The architecture must support fast solo iteration without requiring a
109
+ large platform migration before useful checks land.
110
+
111
+ NFR8: Generated n8n workflow code must follow n8n workflow SDK design guidance:
112
+ normalize data early, avoid accidental per-item fanout, and validate workflow
113
+ code before creation.
114
+
115
+ ## Scope
116
+
117
+ In scope:
118
+
119
+ - Fleet contract design.
120
+ - `pjangler` status, reconcile, validation, and release gates.
121
+ - n8n workflow generation artifacts and first supervisor workflow path.
122
+ - Systemd fallback heartbeat/checkpoint strategy.
123
+ - Current drift repair stories.
124
+
125
+ Out of scope for the first implementation:
126
+
127
+ - Rewriting Hermes internals beyond the minimal profile/config validation needed.
128
+ - Migrating every agent to custom n8n nodes.
129
+ - Removing systemd.
130
+ - Making n8n the Fleet source of truth.
131
+
132
+ ## Success Criteria
133
+
134
+ - `pj templates status` clearly explains which template bytes will be used.
135
+ - `pj fleet status` reports registry/profile/systemd/schema drift without
136
+ destructive changes.
137
+ - `pj fleet reconcile --apply` fixes safe drift and labels unsafe drift manual.
138
+ - `npm run prepublishOnly` fails if vendored templates are dirty.
139
+ - A generated n8n Fleet supervisor workflow can be validated before creation.
140
+ - Agents continue local fallback behavior when n8n is unavailable.
141
+
@@ -26,12 +26,12 @@ invocations).
26
26
  | 00 banner | Print identity | n/a |
27
27
  | 01 config | Seed `~/.config/hermes-agent-template/config.toml` from the shipped example if absent (see [Configuration](#configuration)) | n/a |
28
28
  | 05 fleet env | Ensure `~/.hermes/fleet.env` exists (shared Hermes binary/repo/registry source-of-truth), populated from `config.toml` | n/a |
29
- | 10 hermes profile | `hermes profile create <repo>-<role> --inherit-config --no-alias` + mirror local skills/plugins/hooks from default + symlink canonical runtime skills (`delonet-conventions`, `delonet-dotenv`, `hermes-pm-template-maintenance`, `hindsight`, `subagent-driven-development`) from `/home/delorenj/.agents/skills` | n/a |
30
- | 20 runtime repo | Create gh:delorenj/agent-hm-<repo>-<role> (private), push scaffold from role-local `.runtime-scaffold/`, submodule-add into ./runtime/, symlink ~/.hermes/profiles/<id> → runtime | `SKIP_RUNTIME_REPO=1` |
31
- | 30 telegram | Capture BotFather token, write to profile-local `.env`, enable hermes-telegram toolset | `SKIP_TELEGRAM=1` |
29
+ | 10 hermes profile | `hermes profile create <repo>-<role> --clone --no-alias` + mirror skills/plugins/hooks from default + symlink canonical runtime skills (`delonet-conventions`, `delonet-dotenv`, `hermes-pm-template-maintenance`, `hindsight`, `subagent-driven-development`) from `/home/delorenj/.agents/skills`; PM roles also seed `VOX_URL` in profile `.env` | n/a |
30
+ | 20 runtime repo | Create gh:delorenj/agent-hm-<repo>-<role> (private), push scaffold from role-local `.runtime-scaffold/`, submodule-add into ./runtime/, symlink ~/.hermes/profiles/<id> → runtime; PM roles also link the Voxxy plugin and set `tts.provider: voxxy` | `SKIP_RUNTIME_REPO=1` |
31
+ | 30 telegram | Capture BotFather token, write to runtime/.env, enable hermes-telegram toolset | `SKIP_TELEGRAM=1` |
32
32
  | 40 plane | Create Plane project in 33god workspace (1:1 with agent), patch identifier into role.yaml | `SKIP_PLANE=1` |
33
33
  | 60 bloodbank | Install consumer (renders from scaffold w/ agent values), health-check NATS, install nats-py via uv if missing | `SKIP_BLOODBANK=1` |
34
- | 70 systemd | Install user units: gateway, consumer, hourly checkpoint timer | `SKIP_SYSTEMD=1` |
34
+ | 70 systemd | Install user units: gateway, consumer, heartbeat timer (board-reconciliation sentinel pass + gated runtime checkpoint, one tick) | `SKIP_SYSTEMD=1` |
35
35
  | 80 registry | Append entry to ~/.hermes/agents-registry.yaml | n/a |
36
36
  | 99 summary | Print summary | n/a |
37
37
 
@@ -57,6 +57,8 @@ hermes_repo = "/path/to/hermes-agent"
57
57
  oauth_file = "~/.hermes/auth.json"
58
58
  codex_home = "~/.codex"
59
59
  canonical_skills_dir = "/path/to/.agents/skills"
60
+ voxxy_plugin_dir = "~/code/voxxy/plugins/tts/voxxy"
61
+ vox_url = "https://vox.delo.sh"
60
62
 
61
63
  [github]
62
64
  runtime_repo_owner = "your-gh-owner"
@@ -103,7 +105,7 @@ cd /home/delorenj/code/hermes-agent-template
103
105
  ```bash
104
106
  AGENT=bloodbank-pm
105
107
  systemctl --user start hermes-${AGENT}-consumer.service
106
- systemctl --user start hermes-${AGENT}-checkpoint.timer
108
+ systemctl --user start hermes-${AGENT}-heartbeat.timer
107
109
 
108
110
  # Gateway will fail to start until Telegram is wired up (no other platforms
109
111
  # configured). After running .scripts/30-telegram.sh:
@@ -165,17 +167,12 @@ op read 'op://DeLoSecrets/agent-hm-<repo>-<role>/.env' > agents/hermes/<role>/ru
165
167
 
166
168
  # Symlink the profile dir
167
169
  ln -sfn $PWD/agents/hermes/<role>/runtime ~/.hermes/profiles/<repo>-<role>
168
- cat > agents/hermes/<role>/runtime/profile.yaml <<'YAML'
169
- config:
170
- inherit_from: default
171
- save_mode: delta
172
- YAML
173
170
 
174
171
  # Re-enable systemd units
175
172
  systemctl --user enable hermes-<repo>-<role>-{gateway,consumer}.service
176
- systemctl --user enable hermes-<repo>-<role>-checkpoint.timer
173
+ systemctl --user enable hermes-<repo>-<role>-heartbeat.timer
177
174
  systemctl --user start hermes-<repo>-<role>-{consumer}.service
178
- systemctl --user start hermes-<repo>-<role>-checkpoint.timer
175
+ systemctl --user start hermes-<repo>-<role>-heartbeat.timer
179
176
  ```
180
177
 
181
178
  ## Retire an agent (manual, until v1.1 ships retire.sh)
@@ -184,7 +181,7 @@ systemctl --user start hermes-<repo>-<role>-checkpoint.timer
184
181
  AGENT=bloodbank-dev
185
182
  # 1. Stop daemons
186
183
  systemctl --user disable --now hermes-${AGENT}-{gateway,consumer}.service
187
- systemctl --user disable --now hermes-${AGENT}-checkpoint.timer
184
+ systemctl --user disable --now hermes-${AGENT}-heartbeat.timer
188
185
 
189
186
  # 2. Delete hermes profile (cascades to symlinked runtime — make sure
190
187
  # that's what you want!)
@@ -224,15 +221,16 @@ rm -rf agents/hermes/<role>
224
221
  - Tail consumer: `journalctl --user -fu hermes-<agent>-consumer.service`
225
222
  - Make sure something is actually publishing to `bloodbank.evt.v1.repo.<repo>.*`
226
223
 
227
- ### Checkpoint timer not pushing
228
- - Look at the most recent checkpoint log: `tail ~/.hermes/profiles/<agent>/logs/checkpoint.log`
224
+ ### Heartbeat not checkpointing (runtime not pushing)
225
+ The checkpoint runs inside the heartbeat tick (after the board-reconciliation
226
+ sentinel pass), gated to at most once an hour.
227
+ - Look at the most recent heartbeat log: `tail <role>/runtime/logs/heartbeat.log`
229
228
  - Verify the submodule's git remote is reachable: `cd ...runtime && git push origin HEAD`
230
229
  - If LFS items fail to push: `git lfs push origin HEAD --all`
230
+ - Force a checkpoint out-of-band: `bash agents/hermes/<role>/.scripts/checkpoint.sh`
231
231
 
232
232
  ### Profile dir contains nested `profiles/profiles/...`
233
- - That was a legacy clone-based profile bug. New profiles use
234
- `--inherit-config`; if you see nested profile dirs, remove the nested tree and
235
- confirm `runtime/profile.yaml` contains `config.inherit_from: default`.
233
+ - That was a `--clone-all` bug; we switched to `--clone`. If you see it, just `rm -rf` the nested tree. The template's 10-hermes-profile.sh also has a belt-and-suspenders rm.
236
234
 
237
235
  ### `hermes` launcher complains about HERMES_BIN
238
- - Check the launcher script: `./agents/hermes/<role>/hermes` references `/home/delorenj/code/hermes-agent/.venv/bin/hermes`. Override with `HERMES_BIN=/path/to/hermes ./agents/hermes/pm/hermes status`.
236
+ - Check the launcher script: `./agents/hermes/<role>/hermes` falls back to `$HOME/.hermes/hermes-agent/.venv/bin/hermes` (after `$HERMES_BIN`, `fleet.env`, and config.toml). Override with `HERMES_BIN=/path/to/hermes ./agents/hermes/pm/hermes status`.
@@ -2,20 +2,24 @@
2
2
 
3
3
  Status: Active operations runbook
4
4
  Applies to: every Hermes agent whose `runtime/` is a git submodule checkpointed
5
- by a `hermes-<agent>-<role>-checkpoint.service` systemd unit.
6
-
7
- This runbook fixes the failure where the hourly checkpoint service dies with
8
- **exit 128** and the agent's runtime "brain" stops being backed up. It is
9
- written so any agent or operator can copy it to another repo and apply it
10
- safely. First captured 2026-06-01 fixing the Drumjangler PM checkpoint; the same
11
- fault was present on other agents in the fleet.
5
+ by its `hermes-<agent>-heartbeat.service` systemd unit (the heartbeat runner
6
+ calls `.scripts/checkpoint.sh` to commit+push the runtime).
7
+
8
+ This runbook fixes the failure where the runtime checkpoint dies with
9
+ **exit 128** and the agent's runtime "brain" stops being backed up. The
10
+ checkpoint now runs inside the fused heartbeat tick (board-reconciliation
11
+ sentinel pass + gated checkpoint), so the failing unit is the agent's
12
+ `heartbeat.service`. It is written so any agent or operator can copy it to
13
+ another repo and apply it safely. First captured 2026-06-01 fixing the
14
+ Drumjangler PM checkpoint; the same fault was present on other agents in the
15
+ fleet.
12
16
 
13
17
  ## Symptom
14
18
 
15
19
  ```bash
16
- systemctl --user status hermes-<agent>-<role>-checkpoint.service
20
+ systemctl --user status hermes-<agent>-heartbeat.service
17
21
  # Active: failed (Result: exit-code) ... status=128/n/a
18
- # ExecStart=.../agents/hermes/<role>/.scripts/checkpoint.sh
22
+ # the heartbeat invokes .../agents/hermes/<role>/.scripts/checkpoint.sh
19
23
  ```
20
24
 
21
25
  `checkpoint.sh` is just `cd runtime && git add -A && commit && push`. Run it by
@@ -123,9 +127,9 @@ git add -A --dry-run | awk '{print $2}' | while read -r f; do
123
127
 
124
128
  ```bash
125
129
  bash agents/hermes/<role>/.scripts/checkpoint.sh # exits 0, pushes brain
126
- systemctl --user reset-failed hermes-<agent>-<role>-checkpoint.service
127
- systemctl --user start hermes-<agent>-<role>-checkpoint.service
128
- systemctl --user show hermes-<agent>-<role>-checkpoint.service -p Result -p ExecMainStatus
130
+ systemctl --user reset-failed hermes-<agent>-heartbeat.service
131
+ systemctl --user start hermes-<agent>-heartbeat.service
132
+ systemctl --user show hermes-<agent>-heartbeat.service -p Result -p ExecMainStatus
129
133
  # Result=success ExecMainStatus=0
130
134
  ```
131
135
 
@@ -147,7 +151,7 @@ systemctl --user reset-failed <svc> && systemctl --user start <svc>
147
151
  ## Fleet sweep — this is rarely just one repo
148
152
 
149
153
  ```bash
150
- systemctl --user list-units --all '*-checkpoint.service' | grep -i failed
154
+ systemctl --user list-units --all '*-heartbeat.service' | grep -i failed
151
155
  # for each failing agent, locate its runtime and run the repair script.
152
156
  ```
153
157
 
@@ -1,21 +1,29 @@
1
- # Scrum Master sentinel — developer handoff
2
-
3
- This guide is the entry point for continuing development of the **Scrum Master**
4
- role: a provider-agnostic, always-on ticket sentinel with an autonomous
5
- adversarial review (act, do not wait). Read this first, then follow the links to
6
- the deeper guides. It's written so a Hermes agent (or a human) can pick up the
7
- work without prior context.
8
-
9
- ## What the Scrum Master is
10
-
11
- The Scrum Master is a Hermes agent role whose core loop keeps a project moving:
12
- it makes sure exactly one implementation worker is advancing a ready ticket, or
13
- it records why none can. When a ticket reaches the review lane, it runs an
14
- independent, rigorous **adversarial review** and acts on the verdict
15
- autonomously never parking work to wait on the operator. If the work hasn't
16
- drifted from the operator's locked intent and clears the gate, the review is
17
- `accepted`: the loop treats the ticket as done, unblocks dependents, and emits a
18
- decision event. A real finding is `held` and the ticket goes back to active.
1
+ # Heartbeat sentinel — developer handoff
2
+
3
+ This guide is the entry point for continuing development of the **PM's heartbeat
4
+ sentinel**: a provider-agnostic, always-on ticket-reconciliation engine with an
5
+ autonomous adversarial review (act, do not wait). It is owned by the unified
6
+ `pm` role and runs out-of-band on the PM's heartbeat timer. Read this first,
7
+ then follow the links to the deeper guides. It's written so a Hermes agent (or a
8
+ human) can pick up the work without prior context.
9
+
10
+ ## What the heartbeat sentinel is
11
+
12
+ The sentinel is the PM's continuous board-reconciliation loop, and its job is to
13
+ keep a project moving: it makes sure exactly one implementation worker is
14
+ advancing a ready ticket, or it records why none can. When a ticket reaches the
15
+ review lane, it runs an independent, rigorous **adversarial review** and acts on
16
+ the verdict autonomously never parking work to wait on the operator. If the
17
+ work hasn't drifted from the operator's locked intent and clears the gate, the
18
+ review is `accepted`: the loop treats the ticket as done, unblocks dependents,
19
+ and emits a decision event. A real finding is `held` and the ticket goes back to
20
+ active.
21
+
22
+ There is no longer a separate `scrum-master` role; the sentinel folded into the
23
+ PM and now runs as part of the PM's **heartbeat** — a fused systemd-timer tick
24
+ that does the board-reconciliation pass and then a gated runtime checkpoint
25
+ (`.scripts/heartbeat.sh`). The engine itself lives under the PM at
26
+ `.scripts/sentinel/`.
19
27
 
20
28
  It talks to the ticket board through a pluggable adapter, so the same engine
21
29
  runs on Linear, Plane, or Trello. The engine lives once in this template (the
@@ -29,18 +37,16 @@ The following is true as of June 1, 2026.
29
37
  - The engine, the provider abstraction, and the autonomous adversarial-review
30
38
  enforcement are built, syntax-clean, and validated offline with a mock
31
39
  provider.
32
- - The **Linear** adapter is verified live against a real board (Drumjangler's
40
+ - The **Linear** adapter is verified live against a real board (a real
33
41
  `DEL` team: `resolve`, `list_issues` returning 93 issues, and `get_issue`).
34
42
  - The **Plane** adapter is verified live against a real workspace
35
43
  (`resolve`, `list_issues`, `get_issue`, `comment`, and a
36
44
  `transition` to `completed` on a disposable issue).
37
45
  - The **Trello** adapter is implemented against the same contract but is
38
46
  **not** yet verified against a live board.
39
- - The engine runs on **Linux** (`systemd`) and **macOS** (`launchd`). The runner
40
- uses `flock` on Linux and an atomic `mkdir` lock on macOS.
41
- - **Drumjangler** has been cut over to this engine (provider `linear`). Its
42
- bespoke sentinel is retired. See
43
- [Development guide: the Drumjangler cutover](development.md#the-drumjangler-cutover).
47
+ - The heartbeat runner uses `flock` on Linux and an atomic `mkdir` lock on macOS
48
+ so concurrent ticks never overlap. The systemd timer (Linux) is the primary
49
+ scheduler; a `launchd` agent covers macOS.
44
50
 
45
51
  ## Quick local install
46
52
 
@@ -53,10 +59,9 @@ curl -fsSL https://raw.githubusercontent.com/delorenj/hermes-agent-template/main
53
59
  ```
54
60
 
55
61
  It installs `hermes` and `copier` if missing, writes a host-correct local
56
- config, provisions the PM and Scrum Master roles, binds the Scrum Master to an
57
- existing board, and installs the sentinel as a `launchd` agent (macOS) or
58
- `systemd` timer (Linux). See [Development guide: local
59
- install](development.md#local-install-one-command).
62
+ config, provisions the `pm` role, binds the PM to an existing board, and
63
+ installs the PM's heartbeat timer (`systemd` on Linux, `launchd` agent on
64
+ macOS). See [Development guide: local install](development.md#local-install-one-command).
60
65
 
61
66
  <!-- prettier-ignore -->
62
67
  > [!IMPORTANT]
@@ -64,7 +69,7 @@ install](development.md#local-install-one-command).
64
69
  > runtime repo, a Telegram bot, and a Plane project, and the Telegram step is
65
70
  > interactive. Use `install-local.sh` or the `SKIP_*` flags described in
66
71
  > [Development guide:
67
- > provisioning](development.md#provisioning-a-scrum-master-manual) for local or lean
72
+ > provisioning](development.md#provisioning-the-pm-manual) for local or lean
68
73
  > installs.
69
74
 
70
75
  ## Where the pieces live
@@ -73,21 +78,20 @@ All paths are relative to the repository root.
73
78
 
74
79
  | Path | What it is |
75
80
  | --- | --- |
76
- | `copier.yml` | Questions (`role`, `ticket_provider`, `with_scrum_master`) and the `_tasks` provisioning chain. |
77
- | `template/role.yaml.jinja` | The rendered role manifest, including the `ticket_provider` binding and `scrum_master` knobs. |
81
+ | `copier.yml` | Questions (`role`, `ticket_provider`) and the `_tasks` provisioning chain. |
82
+ | `template/role.yaml.jinja` | The rendered role manifest, including the `ticket_provider` binding and the `reconcile` knobs (`grace_hours`, `auto_review`). |
78
83
  | `template/.scripts/lib/ticket-provider.sh` | The adapter dispatcher (`tp`). The engine's only seam to a ticket system. |
79
84
  | `template/.scripts/providers/{linear,plane,trello}.sh` | The provider adapters. |
80
85
  | `template/.scripts/42-ticket-provider.sh` | Provisioning step that resolves or creates the board. |
81
- | `template/.scripts/75-scrum-master.sh` | Provisioning step that installs the sentinel scheduler (`systemd` timer on Linux, `launchd` agent on macOS). |
82
- | `template/.scripts/90-chain-scrum-master.sh` | Chains a Scrum Master provision when a PM opts in. |
86
+ | `template/.scripts/70-systemd.sh` | Provisioning step that installs the gateway, consumer, and the fused `heartbeat` timer (board-reconciliation sentinel pass + gated runtime checkpoint). |
83
87
  | `install-local.sh` | One-command local install (no cloud, macOS + Linux). |
84
- | `template/.scripts/scrum-master/continuous-ticket-sentinel.sh` | The runner (heartbeat plus full-pass dispatch). |
85
- | `template/.scripts/scrum-master/continuous-ticket-sentinel.prompt.md.jinja` | The prompt the runner feeds to Hermes for a full pass. |
86
- | `template/.scripts/scrum-master/bin/` | Enforcement tools: `issue-autonomous-review.sh`, `issue-close-gate.sh`, `emit-event.py`. |
87
- | `template/.scripts/scrum-master/docs/` | Runtime protocol docs shipped to each deployment. |
88
+ | `template/.scripts/heartbeat.sh` | The heartbeat runner: the sentinel full-pass dispatch (with its own cooldown/lock) plus the gated runtime checkpoint, fused into one tick. |
89
+ | `template/.scripts/sentinel.prompt.md.jinja` | The prompt the runner feeds to Hermes for a full reconciliation pass (rendered to `.scripts/sentinel.prompt.md`). |
90
+ | `template/.scripts/sentinel/bin/` | Enforcement tools: `issue-autonomous-review.sh`, `issue-close-gate.sh`, `emit-event.py`. |
91
+ | `template/.scripts/sentinel/docs/` | Runtime protocol docs shipped to each deployment. |
88
92
 
89
- The `docs/scrum-master/` directory you're reading now holds the **developer**
90
- docs for this template. The `template/.scripts/scrum-master/docs/` directory
93
+ The `docs/sentinel/` directory you're reading now holds the **developer**
94
+ docs for this template. The `template/.scripts/sentinel/docs/` directory
91
95
  holds the **runtime** protocol docs that ship into each provisioned project for
92
96
  the agent to read at run time. Keep the two in sync when behavior changes.
93
97
 
@@ -98,8 +102,8 @@ the agent to read at run time. Keep the two in sync when behavior changes.
98
102
  - [Providers](providers.md): the adapter contract reference and a step-by-step
99
103
  guide to adding or verifying a provider.
100
104
  - [Development guide](development.md): the edit, validate, and propagate
101
- workflow; provisioning and its `SKIP_*` flags; the Drumjangler cutover record;
102
- known gotchas; and the open roadmap.
105
+ workflow; provisioning and its `SKIP_*` flags; known gotchas; and the open
106
+ roadmap.
103
107
 
104
108
  ## Next steps for the incoming agent
105
109
 
@@ -111,8 +115,5 @@ The highest-value open work, in order:
111
115
  2. Confirm `install-local.sh` on a real macOS machine. The Linux path and the
112
116
  Plane adapter are verified; the macOS `launchd` agent and `mkdir` lock get
113
117
  their first real run on a Mac.
114
- 3. Give the Drumjangler Scrum Master its own runtime repo instead of the shared
115
- symlink. See [Development guide: open
116
- roadmap](development.md#open-roadmap).
117
- 4. Confirm the first full Hermes pass after a live cutover reconciles the board
118
- cleanly.
118
+ 3. Confirm the first full Hermes reconciliation pass on a freshly provisioned PM
119
+ reconciles the board cleanly.
@@ -1,36 +1,35 @@
1
- # Scrum Master architecture
1
+ # Heartbeat sentinel architecture
2
2
 
3
- This guide explains how the Scrum Master engine works: the watch loop, the
4
- provider abstraction, the normalized state model, and the autonomous
3
+ This guide explains how the heartbeat sentinel engine works: the heartbeat loop,
4
+ the provider abstraction, the normalized state model, and the autonomous
5
5
  adversarial-review protocol. Read the [handoff overview](README.md) first for the
6
6
  big picture and the file map.
7
7
 
8
- ## The role
8
+ ## Who owns it
9
9
 
10
- The Scrum Master is a first-class Hermes role, a peer of the `pm` role. It's
11
- materialized into a project at `agents/hermes/scrum-master/` and carries its own
12
- `role.yaml`, runtime, prompt, enforcement tools, and a scheduler unit
13
- (`systemd` on Linux, `launchd` on macOS). The role id is `scrum-master`, and the
14
- display name is `<Repo> Scrum Master`.
10
+ The sentinel is owned by the unified `pm` role. It's materialized into a project
11
+ at `agents/hermes/pm/`, where the PM carries its `role.yaml`, runtime, the
12
+ sentinel prompt, the enforcement tools under `.scripts/sentinel/`, and the
13
+ heartbeat runner. There is no separate `scrum-master` role; its
14
+ continuous-ticket-sentinel duties fold into the PM and run out-of-band on the
15
+ PM's heartbeat timer.
15
16
 
16
- You can provision it two ways:
17
+ You provision it by provisioning the PM: run Copier with `--data role=pm` (the
18
+ `70-systemd.sh` step installs the fused `heartbeat` timer that drives the
19
+ sentinel). For the exact commands, see [Development guide:
20
+ provisioning](development.md#provisioning-the-pm-manual).
17
21
 
18
- - **Standalone**, by running Copier with `--data role=scrum-master`.
19
- - **As a PM add-on**, by answering `yes` to the `with_scrum_master` question
20
- during a `pm` provision. That chains a Scrum Master provision for the same
21
- repo and provider through `90-chain-scrum-master.sh`.
22
-
23
- For the exact commands, see [Development guide:
24
- provisioning](development.md#provisioning-a-scrum-master-manual).
25
-
26
- ## The watch loop
22
+ ## The heartbeat loop
27
23
 
28
24
  A scheduler (`systemd` timer on Linux, `launchd` agent on macOS) fires the
29
- runner, `template/.scripts/scrum-master/continuous-ticket-sentinel.sh`, about
30
- once a minute. The runner is a cheap heartbeat that decides whether a full,
31
- LLM-backed pass is worth running. This keeps cost low while staying responsive.
32
-
33
- The heartbeat reads the work-state file,
25
+ runner, `template/.scripts/heartbeat.sh`, about once a minute. Each tick fuses
26
+ two jobs: a board-reconciliation **sentinel pass** and a **gated runtime
27
+ checkpoint**. The sentinel pass is a cheap heartbeat that decides whether a
28
+ full, LLM-backed reconciliation pass is worth running; the checkpoint
29
+ (commit+push of the runtime submodule) is gated to at most once an hour. This
30
+ keeps cost low while staying responsive.
31
+
32
+ The sentinel reads the work-state file,
34
33
  `runtime/continuous-ticket-sentinel-state.json`, and chooses one of these
35
34
  decisions:
36
35
 
@@ -44,7 +43,7 @@ decisions:
44
43
  - `run:full`: none of the above hold, so the runner executes a full pass.
45
44
 
46
45
  A full pass runs `hermes chat` with the rendered prompt,
47
- `continuous-ticket-sentinel.prompt.md`. The prompt tells the agent to reconcile
46
+ `sentinel.prompt.md`. The prompt tells the agent to reconcile
48
47
  the board, the local evidence, and live worker state, then either delegate one
49
48
  worker, monitor the active worker, run an autonomous adversarial review, or
50
49
  record a blocker. A pass never ends in a "looks good, waiting on the operator"
@@ -101,12 +100,12 @@ independent, rigorous **adversarial review** that renders a verdict and the loop
101
100
  **acts on it autonomously**. It is not a narrow escape hatch and not an exception
102
101
  to a no-close rule — it is how reviewed work moves. The full protocol ships into
103
102
  each deployment at
104
- `template/.scripts/scrum-master/docs/autonomous-delegated-review.md`. Here's the
103
+ `template/.scripts/sentinel/docs/autonomous-delegated-review.md`. Here's the
105
104
  shape.
106
105
 
107
106
  When the sentinel finds a ticket in the review lane, it delegates the review to
108
107
  an **independent** reviewer. There is no mandatory grace window:
109
- `scrum_master.grace_hours` defaults to `0`, so the reviewer acts immediately. It
108
+ `reconcile.grace_hours` defaults to `0`, so the reviewer acts immediately. It
110
109
  remains an optional operator knob — set it `>0` to reintroduce a deliberate wait
111
110
  — but the default never parks completed work waiting on the operator. The
112
111
  reviewer must not be the agent that implemented the ticket. Acting as an
@@ -117,7 +116,7 @@ the project's horizon model, and the product north star.
117
116
  The reviewer then runs the decision gate:
118
117
 
119
118
  ```bash
120
- .scripts/scrum-master/bin/issue-autonomous-review.sh <ISSUE> <REPORT> --close
119
+ .scripts/sentinel/bin/issue-autonomous-review.sh <ISSUE> <REPORT> --close
121
120
  ```
122
121
 
123
122
  This script couples four checks so an acceptance can't slip through on a weak
@@ -173,8 +172,8 @@ The event carries `issue`, `decision`, `drift`, `close_gate`, `reviewer_agent`,
173
172
  `evidence_file`, and `report_file`. Together with the
174
173
  `issue.review_rollback.recorded` event it forms the operator's queryable
175
174
  accountability trail for every autonomous decision. The emitter is
176
- `template/.scripts/scrum-master/bin/emit-event.py`, and the event types are
177
- documented in `template/.scripts/scrum-master/docs/bloodbank-events.md`. Events
175
+ `template/.scripts/sentinel/bin/emit-event.py`, and the event types are
176
+ documented in `template/.scripts/sentinel/docs/bloodbank-events.md`. Events
178
177
  append to `_bmad-output/implementation-artifacts/bloodbank-events.jsonl`, a
179
178
  local spool that doesn't require NATS, so the loop stays reliable offline.
180
179
 
@@ -182,18 +181,20 @@ local spool that doesn't require NATS, so the loop stays reliable offline.
182
181
 
183
182
  A full pass moves through these components in order:
184
183
 
185
- 1. The scheduler (`systemd` timer or `launchd` agent) triggers the runner.
186
- 2. The runner's heartbeat decides `run:full` and calls `hermes chat` with the
187
- prompt.
184
+ 1. The scheduler (`systemd` timer or `launchd` agent) triggers the heartbeat
185
+ runner (`heartbeat.sh`).
186
+ 2. The runner's sentinel logic decides `run:full` and calls `hermes chat` with
187
+ the prompt.
188
188
  3. The agent reads the runtime protocol docs and reconciles state by calling
189
189
  `tp` (the adapter) for board data.
190
190
  4. The agent delegates a worker, monitors one, records a blocker, or runs an
191
191
  autonomous adversarial review.
192
- 5. An adversarial review runs the enforcement tools in `bin/`, which render an
193
- `accepted` or `held` verdict and emit a decision event; the loop acts on the
194
- verdict (treat as done and unblock dependents, or send the ticket back to
195
- active) without waiting on the operator.
196
- 6. The runner writes the outcome to the state file for the next heartbeat.
192
+ 5. An adversarial review runs the enforcement tools in `.scripts/sentinel/bin/`,
193
+ which render an `accepted` or `held` verdict and emit a decision event; the
194
+ loop acts on the verdict (treat as done and unblock dependents, or send the
195
+ ticket back to active) without waiting on the operator.
196
+ 6. The runner writes the outcome to the state file for the next tick, then
197
+ opportunistically checkpoints the runtime (gated to ~hourly) before exiting.
197
198
 
198
199
  ## Read next
199
200