@delorenj/pjangler 1.2.19 → 1.2.25

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 (72) hide show
  1. package/dist/index.js +2353 -518
  2. package/dist/mcp-server.js +2331 -494
  3. package/package.json +14 -3
  4. package/templates/commonproject/copier.yml +6 -4
  5. package/templates/commonproject/template/.agents/hooks/README.md +13 -26
  6. package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +4 -14
  7. package/templates/commonproject/template/.agents/hooks/sync.py +5 -6
  8. package/templates/commonproject/template/.agents/local.example.json +2 -8
  9. package/templates/commonproject/template/.agents/skills.json +6 -0
  10. package/templates/commonproject/template/.mise/scripts/provision-bmad-skills.py +480 -0
  11. package/templates/commonproject/template/.mise/scripts/sync-skills.py +449 -0
  12. package/templates/commonproject/template/mise.toml.jinja +16 -16
  13. package/templates/commonproject/tests/test_provision_bmad_skills.py +203 -0
  14. package/templates/commonproject/tests/test_sync_skills_topology.py +120 -0
  15. package/templates/hermes-agent/README.md +70 -29
  16. package/templates/hermes-agent/copier.yml +9 -6
  17. package/templates/hermes-agent/docs/architecture.md +75 -51
  18. package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +11 -6
  19. package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +2 -2
  20. package/templates/hermes-agent/docs/fleet-control-plane/prd.md +2 -3
  21. package/templates/hermes-agent/docs/operations.md +179 -81
  22. package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +31 -171
  23. package/templates/hermes-agent/docs/sentinel/README.md +10 -10
  24. package/templates/hermes-agent/docs/sentinel/architecture.md +6 -8
  25. package/templates/hermes-agent/docs/sentinel/development.md +12 -15
  26. package/templates/hermes-agent/docs/sentinel.md +1 -1
  27. package/templates/hermes-agent/install-local.sh +41 -4
  28. package/templates/hermes-agent/runtime-scaffold/README.md +4 -2
  29. package/templates/hermes-agent/scripts/degit-runtime.sh +192 -0
  30. package/templates/hermes-agent/scripts/fleet-prune-debris.sh +128 -0
  31. package/templates/hermes-agent/scripts/fleet-sync.sh +164 -7
  32. package/templates/hermes-agent/scripts/hermes-runtime-templatize.py +321 -0
  33. package/templates/hermes-agent/scripts/migrate-profile-scoped-chat-secrets.py +136 -0
  34. package/templates/hermes-agent/scripts/momo-unify-agent.py +224 -0
  35. package/templates/hermes-agent/scripts/repair-runtime-checkpoint.sh +7 -119
  36. package/templates/hermes-agent/template/.gitignore.jinja +5 -2
  37. package/templates/hermes-agent/template/.runtime-scaffold/README.md +4 -2
  38. package/templates/hermes-agent/template/.scripts/00-banner.sh +1 -1
  39. package/templates/hermes-agent/template/.scripts/01-config.sh +3 -0
  40. package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +12 -1
  41. package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +55 -129
  42. package/templates/hermes-agent/template/.scripts/30-telegram.sh +276 -22
  43. package/templates/hermes-agent/template/.scripts/31-slack.sh +362 -0
  44. package/templates/hermes-agent/template/.scripts/60-bloodbank.sh +8 -66
  45. package/templates/hermes-agent/template/.scripts/70-systemd.sh +44 -30
  46. package/templates/hermes-agent/template/.scripts/80-registry.sh +93 -13
  47. package/templates/hermes-agent/template/.scripts/99-summary.sh +1 -4
  48. package/templates/hermes-agent/template/.scripts/_lib.sh +66 -1
  49. package/templates/hermes-agent/template/.scripts/checkpoint.sh +3 -2
  50. package/templates/hermes-agent/template/.scripts/config.example.toml +10 -10
  51. package/templates/hermes-agent/template/.scripts/heartbeat.sh +12 -1
  52. package/templates/hermes-agent/template/.scripts/momo-wip-lock.py +137 -0
  53. package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +21 -0
  54. package/templates/hermes-agent/template/SOUL.md.jinja +36 -15
  55. package/templates/hermes-agent/template/hermes.jinja +6 -3
  56. package/templates/hermes-agent/template/role.yaml.jinja +24 -13
  57. package/templates/hermes-agent/tests/test_bloodbank_consumer_contract.py +429 -130
  58. package/templates/hermes-agent/tests/test_fleet_upgrade_contract.py +176 -0
  59. package/templates/hermes-agent/tests/test_launcher_chat_secret_isolation.py +85 -0
  60. package/templates/hermes-agent/tests/test_local_runtime_provisioning.py +130 -0
  61. package/templates/hermes-agent/tests/test_operator_docs_local_runtime_contract.py +132 -0
  62. package/templates/hermes-agent/tests/test_profile_env_detach_contract.py +14 -0
  63. package/templates/hermes-agent/tests/test_profile_scoped_chat_secrets.py +89 -0
  64. package/templates/hermes-agent/tests/test_repository_privacy_contract.py +32 -0
  65. package/templates/hermes-agent/tests/test_slack_provisioning.py +344 -0
  66. package/templates/hermes-agent/tests/test_telegram_provisioning.py +475 -0
  67. package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +0 -110
  68. package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +0 -45
  69. package/templates/hermes-agent/.codegraph/daemon.pid +0 -6
  70. package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +0 -10
  71. package/templates/hermes-agent/runtime-scaffold/bloodbank-consumer.py +0 -181
  72. package/templates/hermes-agent/template/.runtime-scaffold/bloodbank-consumer.py +0 -181
@@ -1,6 +1,6 @@
1
1
  # Architecture
2
2
 
3
- ## Two-artifact split
3
+ ## Tracked role and local runtime split
4
4
 
5
5
  ```
6
6
  ┌──────────────────────────────────────────────────────────────────────────┐
@@ -20,77 +20,79 @@
20
20
  │ ├── SOUL.md │
21
21
  │ ├── hermes (launcher) │
22
22
  │ ├── .scripts/ │
23
- │ └── runtime/ ────────────┼─── git submodule
24
- └────────────────────────────────┘
25
-
26
- ┌────────────────────────────────────────┐
27
- gh:delorenj/agent-hm-<project>-<role> ← NEW repo per agent
28
- │ ───────────────────────────────────── │ private
29
- │ ├── config.yaml │
30
- │ ├── SOUL.md (evolving) │
31
- │ ├── memories/ │ auto-checkpointed
32
- │ ├── sessions/sessions.db (LFS) │ by the heartbeat
33
- │ ├── decisions/ │ + on session end
34
- │ └── bloodbank-consumer.py │
35
- └────────────────────────────────────────┘
23
+ │ └── runtime/ ignored local HERMES_HOME
24
+ ├── config.yaml │
25
+ │ ├── memories/ │
26
+ │ ├── sessions/ │
27
+ └── .env
28
+ └─────────────────────────────────────────┘
36
29
  ```
37
30
 
38
- ## Why two artifacts, not one
31
+ ## Why tracked role and local state are separate
39
32
 
40
33
  The **template** is the contract / the bootstrap recipe — it doesn't change
41
- when an agent learns something. The **runtime** is the agent's accumulating
42
- state — it changes every conversation. Bundling them would mean every memory
43
- update churns the template's commit log; separating them means:
34
+ when an agent learns something. The ignored **runtime directory** is the
35
+ agent's accumulating local state — it changes every conversation. Separating
36
+ them means:
44
37
 
45
38
  - The template repo is small, stable, easy to update fleet-wide
46
- - The runtime repo is per-agent, fast-moving, auditable in isolation
47
- - You can fork an agent (branch the runtime repo) without touching others
48
- - You can wipe an agent (delete the runtime repo) without affecting the template
39
+ - Project commits cannot accidentally publish runtime credentials or sessions
40
+ - Each profile has an isolated HERMES_HOME without project-index churn
41
+ - Provisioning can refresh tracked launchers and scaffolds without overwriting
42
+ existing local state
49
43
 
50
44
  A third file ties the fleet together: `~/.hermes/fleet.env`.
51
45
  It is the single source-of-truth pointer for the shared Hermes executable/repo
52
46
  that every generated launcher uses.
53
47
 
54
- ## Why git-tracked runtime
48
+ ## Durability boundary
55
49
 
56
- The runtime is the agent's "subjective experience" its memory of every
57
- conversation, the SOUL refinements it has absorbed, the decisions it has
58
- emitted. Putting it in git gives:
50
+ Ignored local state is not automatically durable. The operator must configure
51
+ an encrypted filesystem backup or snapshot for each exact runtime path. A
52
+ project clone restores only tracked role files and the empty scaffold.
53
+ Hindsight retains only memories/events explicitly written to its bank, while a
54
+ secret manager retains only credentials explicitly stored there; neither is a
55
+ complete runtime backup. See [Operations](operations.md#back-up-and-restore-an-agent).
59
56
 
60
- - **Durability**: nothing lost when the host dies. `git clone` restores it.
61
- - **Auditability**: `git log` is a full trace of how the agent evolved.
62
- - **Reversibility**: if the agent develops bad habits, `git revert` rolls back.
63
- - **Forkability**: experiment with a copy on a branch, merge if it works out.
64
- - **Cross-machine**: same agent state on big-chungus and on the laptop.
65
-
66
- ## Heartbeat cadence (reconcile + checkpoint)
57
+ ## Heartbeat cadence
67
58
 
68
59
  A systemd `--user` timer runs `.scripts/heartbeat.sh` frequently (about once a
69
- minute). Each tick fuses two jobs into one:
60
+ minute). For a pure-local runtime, each tick performs one job:
70
61
 
71
62
  1. **Board-reconciliation sentinel pass** — the PM's continuous ticket sentinel.
72
63
  The runner's own cooldown/lock logic decides whether a full, LLM-backed
73
64
  reconciliation pass is worth running (it rate-limits the expensive Hermes
74
65
  call); see [the sentinel docs](sentinel/README.md).
75
- 2. **Gated runtime checkpoint** after the sentinel decision, the runner calls
76
- `.scripts/checkpoint.sh`, gated to at most once an hour. The checkpoint:
77
- `cd`s into the runtime submodule, `git add -A`, commits only if dirty (exits
78
- clean otherwise), and pushes to `origin`.
79
-
80
- On session end, a hermes hook (TBD path) checkpoints immediately so nothing
81
- in-flight is lost between heartbeat ticks.
82
-
83
- Sensitive state — `.env`, `auth.json`, OAuth tokens — never enters git.
84
- They're in `.gitignore` and live only on the host machine.
66
+ Sensitive state `.env`, `auth.json`, OAuth tokens never enters project Git.
67
+ It lives only in ignored local storage unless the operator separately places a
68
+ credential in the secret manager or includes the runtime in an encrypted
69
+ filesystem backup.
85
70
 
86
71
  ## One bot per agent (Telegram)
87
72
 
88
73
  Each agent gets its own BotFather bot and runs its own gateway daemon.
89
- Hermes' `gateway/status.py:acquire_scoped_lock(scope="telegram", identity=<token>)`
90
- already enforces "one token per gateway process" so even if two profiles
91
- happen to share a token, the second one's startup fails fast. The N×M cost
92
- (N BotFather sessions per fleet) is the price we accept for zero custom
93
- routing code.
74
+ The BotFather token is an invocation-only provisioning input: shared
75
+ `fleet.env` may carry the non-secret allow-list policy but is never allowed to
76
+ supply `TELEGRAM_BOT_TOKEN`. Provisioning verifies `getMe`, rejects a token or
77
+ bot identity already owned anywhere in the local fleet, atomically writes the
78
+ credential only to the profile's mode-`0600` `runtime/.env`, and records only
79
+ safe identity metadata in `role.yaml` and the registry. Hermes' scoped runtime
80
+ lock remains a second line of defense against duplicate pollers.
81
+
82
+ ## One app and bot per Slack-enabled agent
83
+
84
+ Slack is opt-in and remains deferred for newly provisioned agents unless the
85
+ operator explicitly enables it or supplies both required tokens. An enabled
86
+ agent owns one dedicated `xapp-` Socket Mode token and one dedicated `xoxb-`
87
+ bot token; provisioning rejects token reuse and a verified bot identity already
88
+ owned by another registry entry.
89
+
90
+ The bot token is verified through Slack's read-only `auth.test` endpoint.
91
+ Credentials live only in the agent's mode-`0600`, gitignored `runtime/.env`.
92
+ The shared `~/.hermes/.env`, `fleet.env`, `role.yaml`, and fleet registry never
93
+ contain Slack tokens: manifests and registry entries retain only provisioning
94
+ status, workspace identity, and bot identity. The non-secret allowed-user
95
+ policy may be inherited from fleet config.
94
96
 
95
97
  ## One Plane project per agent
96
98
 
@@ -100,12 +102,34 @@ archive-on-retire clean.
100
102
 
101
103
  ## Bloodbank wiring
102
104
 
103
- Each consumer subscribes to two lanes:
104
- - `bloodbank.evt.v1.repo.>` canonical repo-domain events, filtered by `data.repo`
105
- - `bloodbank.cmd.v1.agent.>` — canonical agent-domain commands, filtered by `data.target_agent_id`
105
+ Bloodbank command ingress is owned by one fleet-shared official Hermes gateway,
106
+ not by a consumer in every runtime. Each registry entry advertises:
107
+
108
+ ```yaml
109
+ bloodbank:
110
+ gateway_scope: fleet
111
+ target_agent_id: <agent-id>
112
+ ```
113
+
114
+ The shared gateway subscribes once, resolves `data.target_agent_id` through the
115
+ fleet registry, and routes the turn into that Hermes profile. Per-profile
116
+ messaging gateways and heartbeat timers remain independent; there is no
117
+ per-profile NATS process, systemd consumer unit, or filesystem inbox bridge.
106
118
 
107
119
  Each agent emits CloudEvents 1.0 envelopes with `actor.agent_id`,
108
120
  `producer = hermes-agent:<id>`, `source = hermes://agent/<id>`. The naming
109
121
  contract is owned by Bloodbank (`~/code/33GOD/bloodbank/docs/event-naming.md`).
110
122
  Repo and agent identifiers belong in envelope data, actor, or source fields,
111
123
  never in type or subject tokens.
124
+
125
+ The gateway uses the canonical lifecycle already defined by those schemas:
126
+
127
+ - `bloodbank.v1.conversation.turn.started`
128
+ - `bloodbank.v1.agent.invocation.started`
129
+ - one terminal invocation event: `bloodbank.v1.agent.invocation.completed` or
130
+ `bloodbank.v1.agent.invocation.failed`
131
+ - `bloodbank.v1.conversation.turn.completed`
132
+
133
+ There are no separate `received` or `accepted` lifecycle events. A JetStream
134
+ command is acknowledged only after Hermes processing completion and terminal
135
+ event publication.
@@ -43,7 +43,10 @@ n8n
43
43
  runs visual Fleet workflows generated from pjangler and registry state
44
44
 
45
45
  systemd --user
46
- keeps gateway, consumer, and local fallback heartbeat/checkpoint alive
46
+ keeps each profile gateway and local fallback heartbeat/checkpoint alive
47
+
48
+ fleet Bloodbank gateway
49
+ routes canonical commands to registered target_agent_id values
47
50
  ```
48
51
 
49
52
  ## Architecture Decisions
@@ -115,7 +118,8 @@ Rationale:
115
118
 
116
119
  Consequences:
117
120
 
118
- - systemd keeps gateway and consumer services running.
121
+ - systemd keeps each profile gateway and heartbeat fallback running.
122
+ - The fleet-shared Bloodbank gateway owns command-bus ingress for all profiles.
119
123
  - systemd fallback performs minimal self-health and checkpoint behavior.
120
124
  - n8n performs supervisor flows, agent health fanout, delegation, and
121
125
  reconciliation triggers.
@@ -164,8 +168,10 @@ systemd:
164
168
  fallback: true
165
169
  required_units:
166
170
  - gateway
167
- - consumer
168
171
  - fallback-heartbeat
172
+ bloodbank:
173
+ gateway_scope: fleet
174
+ routing_key: data.target_agent_id
169
175
  ```
170
176
 
171
177
  ## n8n Workflow Model
@@ -205,8 +211,8 @@ Trigger:
205
211
  Flow:
206
212
 
207
213
  1. Load one agent entry.
208
- 2. Check gateway, consumer, fallback heartbeat, profile symlink, runtime repo,
209
- and role manifest.
214
+ 2. Check profile gateway, fleet Bloodbank registration, fallback heartbeat,
215
+ profile symlink, runtime repo, and role manifest.
210
216
  3. If safe drift exists, call `pj fleet reconcile --agent <id> --apply`.
211
217
  4. If unsafe drift exists, emit manual action.
212
218
  5. Record result for supervisor.
@@ -250,4 +256,3 @@ pj fleet n8n create --name <workflow>
250
256
  4. Promote heartbeat v2 contract and systemd fallback behavior.
251
257
  5. Generate and validate n8n supervisor workflow.
252
258
  6. Enable workflow creation only after exported workflow validation passes.
253
-
@@ -201,7 +201,8 @@ models do not coexist accidentally.
201
201
  Acceptance Criteria:
202
202
 
203
203
  - Given `service_model: hybrid-n8n-systemd`, when validation runs, then required
204
- gateway, consumer, and fallback heartbeat expectations are checked.
204
+ profile gateway, fleet Bloodbank registration, and fallback heartbeat
205
+ expectations are checked.
205
206
  - Given a checkpoint-only agent exists during migration, when status runs, then
206
207
  it is classified as legacy rather than broken.
207
208
  - Given a heartbeat-enabled agent exists, when status runs, then n8n registration
@@ -277,4 +278,3 @@ Acceptance Criteria:
277
278
  expected changes without mutation.
278
279
  - Given n8n workflow code is generated, when validation runs, then no workflow is
279
280
  created until validation succeeds.
280
-
@@ -95,8 +95,8 @@ NFR1: Default commands must be read-only or dry-run.
95
95
  NFR2: Reconciliation must never destroy or merge runtime state automatically
96
96
  when profiles or runtime repos contain ambiguous data.
97
97
 
98
- NFR3: n8n outage must not prevent baseline agent health, gateway operation,
99
- consumer operation, or runtime checkpoint fallback.
98
+ NFR3: n8n outage must not prevent baseline agent health, profile gateway
99
+ operation, fleet Bloodbank routing, or runtime checkpoint fallback.
100
100
 
101
101
  NFR4: Validation output must be deterministic and grep-friendly.
102
102
 
@@ -138,4 +138,3 @@ Out of scope for the first implementation:
138
138
  - `npm run prepublishOnly` fails if vendored templates are dirty.
139
139
  - A generated n8n Fleet supervisor workflow can be validated before creation.
140
140
  - Agents continue local fallback behavior when n8n is unavailable.
141
-
@@ -27,11 +27,12 @@ invocations).
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
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` |
30
+ | 20 local runtime | Populate missing files from role-local `.runtime-scaffold/` into ignored `./runtime/`, symlink `~/.hermes/profiles/<id>` → runtime, and refuse stale project gitlinks/mappings; PM roles also link the Voxxy plugin and set `tts.provider: voxxy` | `SKIP_RUNTIME_REPO=1` |
31
+ | 30 telegram | Verify an invocation-supplied, profile-dedicated BotFather token; reject fleet reuse; write only to runtime/.env | `SKIP_TELEGRAM=1` |
32
+ | 31 slack | Disabled/deferred by default; verify a dedicated app+bot pair with `auth.test` and write it only to runtime/.env when explicitly enabled | `SKIP_SLACK=1` |
32
33
  | 40 plane | Create Plane project in 33god workspace (1:1 with agent), patch identifier into role.yaml | `SKIP_PLANE=1` |
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, heartbeat timer (board-reconciliation sentinel pass + gated runtime checkpoint, one tick) | `SKIP_SYSTEMD=1` |
34
+ | 60 bloodbank | Compatibility checkpoint for fleet-shared routing; installs no files, dependencies, or services | `SKIP_BLOODBANK=1` remains a no-op |
35
+ | 70 systemd | Install user units: profile gateway and board-reconciliation heartbeat timer | `SKIP_SYSTEMD=1` |
35
36
  | 80 registry | Append entry to ~/.hermes/agents-registry.yaml | n/a |
36
37
  | 99 summary | Print summary | n/a |
37
38
 
@@ -54,28 +55,23 @@ different machine/user, edit this one file:
54
55
  [fleet]
55
56
  hermes_bin = "/path/to/hermes-agent/.venv/bin/hermes"
56
57
  hermes_repo = "/path/to/hermes-agent"
58
+ hermes_git_url = "https://github.com/delorenj/hermes-agent.git"
59
+ hermes_git_ref = "feature/PJAN-19-routing-publication"
60
+ hermes_git_sha = "113e1b182b6d72a7dd02a191f134a41668ceaf0e"
57
61
  oauth_file = "~/.hermes/auth.json"
58
62
  codex_home = "~/.codex"
59
63
  canonical_skills_dir = "/path/to/.agents/skills"
60
64
  voxxy_plugin_dir = "~/code/voxxy/plugins/tts/voxxy"
61
65
  vox_url = "https://vox.delo.sh"
62
66
 
63
- [github]
64
- runtime_repo_owner = "your-gh-owner"
65
-
66
67
  [plane]
67
68
  base = "https://plane.example.com"
68
69
  workspace = "your-workspace"
69
-
70
- [bloodbank]
71
- nats_host = "127.0.0.1"
72
- nats_port = 4222
73
70
  ```
74
71
 
75
- `role.yaml` stores an empty `runtime.github_owner` and `plane.workspace` for
76
- freshly provisioned agents; the shell layer fills them from `config.toml` at
77
- runtime (older manifests that baked `owner/name` into `runtime.github_repo`
78
- still work unchanged).
72
+ `role.yaml` stores compatibility metadata for older generated roles, but the
73
+ current local-runtime provisioner does not use it for storage. Plane workspace
74
+ defaults are still filled from `config.toml`.
79
75
 
80
76
  ## Fleet source-of-truth
81
77
 
@@ -93,6 +89,13 @@ For Codex auth, run `hermes auth add openai-codex` through any generated agent
93
89
  launcher once; all agents using the same fleet env read the same Hermes OAuth
94
90
  store afterward.
95
91
 
92
+ The reviewed runtime source is the `delorenj/hermes-agent` fork, publication
93
+ ref `feature/PJAN-19-routing-publication`, pinned at
94
+ `113e1b182b6d72a7dd02a191f134a41668ceaf0e`. `install-local.sh` performs a
95
+ single-branch clone, verifies the pin is on that ref, passes both `--branch` and
96
+ `--commit` to the checked-out installer, and refuses an existing checkout with
97
+ a different origin. It never fetches or writes upstream `main`.
98
+
96
99
  To retrofit older provisioned agents onto this model, run:
97
100
 
98
101
  ```bash
@@ -100,15 +103,38 @@ cd /home/delorenj/code/hermes-agent-template
100
103
  ./scripts/backfill-fleet-sot.sh
101
104
  ```
102
105
 
106
+ Then audit legacy consumers before relying on the fleet-shared Bloodbank
107
+ gateway:
108
+
109
+ ```bash
110
+ ./scripts/fleet-sync.sh
111
+ ./scripts/fleet-sync.sh --apply
112
+ ```
113
+
114
+ Any active, enabled, installed, or registry-declared
115
+ `hermes-<agent>-consumer.service` is unhealthy drift. Apply mode disables and
116
+ stops it, proves the unit is explicitly `inactive` and `disabled`, then removes
117
+ the unit, reloads user systemd, and removes the legacy registry metadata. A
118
+ user-manager/query error, failed disable, or ambiguous post-disable state fails
119
+ closed and preserves both the unit file and registry metadata. The same cleanup
120
+ runs in step 70 before its done marker is honored.
121
+
122
+ Fleet registry and chat identity changes share `${registry_file}.lock`. `flock`
123
+ owns the lock in the kernel (an on-disk lock file surviving a crash is safe),
124
+ while atomic replace prevents partial YAML. Registry and lock files are mode
125
+ `0600`; symlink targets are refused. Registry and profile-local credential
126
+ replacements also sync the containing directory where the platform supports it.
127
+ Unexpected directory-sync errors are reported as failures without marking the
128
+ step complete, so rerunning the idempotent provisioning step is safe.
129
+
103
130
  ## Start the daemons for an agent
104
131
 
105
132
  ```bash
106
133
  AGENT=bloodbank-pm
107
- systemctl --user start hermes-${AGENT}-consumer.service
108
134
  systemctl --user start hermes-${AGENT}-heartbeat.timer
109
135
 
110
- # Gateway will fail to start until Telegram is wired up (no other platforms
111
- # configured). After running .scripts/30-telegram.sh:
136
+ # Gateway will fail to start until at least one messaging platform is wired.
137
+ # After running .scripts/30-telegram.sh or .scripts/31-slack.sh:
112
138
  systemctl --user start hermes-${AGENT}-gateway.service
113
139
  ```
114
140
 
@@ -117,8 +143,9 @@ systemctl --user start hermes-${AGENT}-gateway.service
117
143
  | Channel | How |
118
144
  | --- | --- |
119
145
  | Telegram | DM `@<repo>_<role>_bot` (once Telegram is wired) |
146
+ | Slack | DM or mention the verified per-agent Slack bot (once Slack is wired) |
120
147
  | Local CLI | `./agents/hermes/<role>/hermes chat "..."` |
121
- | Bloodbank | Publish to `bloodbank.cmd.v1.agent.task.assign` with `data.target_agent_id = <agent_id>` |
148
+ | Bloodbank | Publish to `bloodbank.cmd.v1.agent.invocation.start` with `data.target_agent_id = <agent_id>` |
122
149
 
123
150
  ## Inspect fleet state
124
151
 
@@ -134,8 +161,8 @@ EOF
134
161
  systemctl --user list-units --state=active 'hermes-*'
135
162
  systemctl --user list-timers 'hermes-*'
136
163
 
137
- # Tail the consumer for live bloodbank events
138
- journalctl --user -fu hermes-<agent-id>-consumer.service
164
+ # Bloodbank routing identity consumed by the fleet-shared gateway
165
+ python3 -c "import yaml,pathlib; print(yaml.safe_load(pathlib.Path.home().joinpath('.hermes/agents-registry.yaml').read_text())['agents']['<agent-id>']['bloodbank'])"
139
166
  ```
140
167
 
141
168
  ## Deferred manual steps (one-time per agent)
@@ -150,65 +177,130 @@ journalctl --user -fu hermes-<agent-id>-consumer.service
150
177
  ```bash
151
178
  cd <project>/agents/hermes/<role>
152
179
  rm .scripts/.done-30-telegram
153
- SKIP_TELEGRAM=0 ./.scripts/30-telegram.sh
180
+ TELEGRAM_BOT_TOKEN='<bot-id>:<secret>' \
181
+ TELEGRAM_ALLOWED_USERS='<your-user-id>' \
182
+ SKIP_TELEGRAM=0 ./.scripts/30-telegram.sh
154
183
  systemctl --user restart hermes-<agent-id>-gateway.service
155
184
  ```
156
185
 
157
- ## Restore an agent on a new machine
186
+ The token is captured before shared fleet configuration is loaded, verified
187
+ through Telegram `getMe`, checked against local token and bot-identity owners,
188
+ and atomically written only to the profile's gitignored `runtime/.env` with
189
+ mode `0600`. `~/.hermes/fleet.env` and `~/.hermes/.env` must not contain
190
+ `TELEGRAM_BOT_TOKEN`; the manifest and registry store only verified bot
191
+ identity metadata. `TELEGRAM_ALLOWED_USERS` is non-secret and may be shared.
192
+
193
+ ### Slack app and bot (opt-in)
194
+
195
+ Slack remains deferred unless `ENABLE_SLACK=1` (also accepts
196
+ `WIRE_SLACK=1`) is set or both credentials are supplied. Each enabled agent
197
+ must have its own Slack app-level Socket Mode token and bot token; neither may
198
+ be reused by another profile.
158
199
 
159
200
  ```bash
160
- cd /path/to/the/project-repo
161
- git submodule update --init --recursive
162
- git -C agents/hermes/<role>/runtime lfs pull
163
-
164
- # Restore secrets that were excluded from git:
165
- op read 'op://DeLoSecrets/agent-hm-<repo>-<role>/.env' > agents/hermes/<role>/runtime/.env
166
- # (or copy from a backup machine)
167
-
168
- # Symlink the profile dir
169
- ln -sfn $PWD/agents/hermes/<role>/runtime ~/.hermes/profiles/<repo>-<role>
170
-
171
- # Re-enable systemd units
172
- systemctl --user enable hermes-<repo>-<role>-{gateway,consumer}.service
173
- systemctl --user enable hermes-<repo>-<role>-heartbeat.timer
174
- systemctl --user start hermes-<repo>-<role>-{consumer}.service
175
- systemctl --user start hermes-<repo>-<role>-heartbeat.timer
201
+ cd <project>/agents/hermes/<role>
202
+ ENABLE_SLACK=1 \
203
+ SLACK_BOT_TOKEN='xoxb-...' \
204
+ SLACK_APP_TOKEN='xapp-...' \
205
+ SLACK_ALLOWED_USERS='U01ABC2DEF3' \
206
+ ./.scripts/31-slack.sh
207
+ systemctl --user restart hermes-<agent-id>-gateway.service
176
208
  ```
177
209
 
178
- ## Retire an agent (manual, until v1.1 ships retire.sh)
210
+ The step calls Slack's read-only `auth.test` endpoint for the bot token, checks
211
+ the local fleet for token or bot-identity reuse, and records only the verified
212
+ workspace/bot identity in `role.yaml` and the fleet registry. Tokens are
213
+ atomically written to the agent's gitignored `runtime/.env` with mode `0600`.
214
+ They must never be placed in `~/.hermes/.env` or `~/.hermes/fleet.env`.
215
+
216
+ `SLACK_ALLOWED_USERS` is non-secret and may instead be set in `fleet.env` as a
217
+ shared policy. An empty allow-list is safe but denies all inbound Slack users.
218
+
219
+ ## Back up and restore an agent
220
+
221
+ The template does **not** ship automatic backup for the ignored
222
+ `agents/hermes/<role>/runtime/` directory. A project clone recreates the
223
+ tracked role and its empty scaffold, not accumulated local state. Configure an
224
+ encrypted filesystem backup or snapshot that includes the exact runtime path
225
+ before treating the agent as recoverable.
226
+
227
+ For a manual transfer, create a private archive, copy it to operator-managed
228
+ encrypted storage, and verify both the checksum and readable member list:
179
229
 
180
230
  ```bash
181
- AGENT=bloodbank-dev
182
- # 1. Stop daemons
183
- systemctl --user disable --now hermes-${AGENT}-{gateway,consumer}.service
184
- systemctl --user disable --now hermes-${AGENT}-heartbeat.timer
185
-
186
- # 2. Delete hermes profile (cascades to symlinked runtime — make sure
187
- # that's what you want!)
188
- hermes profile delete ${AGENT}
189
-
190
- # 3. Archive Plane project (Plane UI or API)
191
- PROJECT_ID=$(python3 -c "import yaml,pathlib; print(yaml.safe_load(pathlib.Path.home().joinpath('.hermes/agents-registry.yaml').read_text())['agents']['${AGENT}']['plane']['project_id'])")
192
- curl -X POST "https://plane.delo.sh/api/v1/workspaces/33god/projects/${PROJECT_ID}/archive/" \
193
- -H "X-API-Key: ${PLANE_33GOD_API_KEY}"
194
-
195
- # 4. BotFather: /deletebot @<repo>_<role>_bot
196
- # 5. Archive runtime repo (GitHub UI; we don't have delete_repo scope by default)
197
- # 6. Remove registry entry
198
- python3 -c "
199
- import yaml, pathlib
200
- p = pathlib.Path.home() / '.hermes' / 'agents-registry.yaml'
201
- d = yaml.safe_load(p.read_text()); d['agents'].pop('${AGENT}', None)
202
- p.write_text(yaml.safe_dump(d))"
203
-
204
- # 7. In the project repo, remove the submodule
205
- cd /path/to/project
206
- git submodule deinit -f agents/hermes/<role>/runtime
207
- git rm -f agents/hermes/<role>/runtime
208
- rm -rf .git/modules/agents/hermes/<role>/runtime
209
- rm -rf agents/hermes/<role>
231
+ PROJECT=/absolute/path/to/project
232
+ ROLE=pm
233
+ BACKUP_DIR="$HOME/.local/state/hermes-runtime-backups"
234
+ mkdir -p -m 0700 "$BACKUP_DIR"
235
+ BACKUP="$BACKUP_DIR/$(basename "$PROJECT")-${ROLE}-$(date -u +%Y%m%dT%H%M%SZ).tar.gz"
236
+ tar -C "$PROJECT" -czf "$BACKUP" "agents/hermes/${ROLE}/runtime"
237
+ chmod 0600 "$BACKUP"
238
+ sha256sum "$BACKUP" > "${BACKUP}.sha256"
239
+ sha256sum -c "${BACKUP}.sha256"
240
+ tar -tzf "$BACKUP" >/dev/null
210
241
  ```
211
242
 
243
+ That archive may contain credentials and private conversations. It is not an
244
+ off-host backup until it has been copied to encrypted storage controlled by the
245
+ operator and independently verified there.
246
+
247
+ Recovery sources are intentionally distinct:
248
+
249
+ - The verified filesystem backup is the only complete source for local config,
250
+ sessions, databases, skills, and other runtime files.
251
+ - Hindsight can restore only memories/events that were previously written to
252
+ its remote bank. It is not a backup of the runtime directory.
253
+ - The secret manager can restore only credentials deliberately stored there;
254
+ it does not contain memories, sessions, or local configuration by default.
255
+ - Re-running provisioning restores the scaffold and service definitions, not
256
+ learned state.
257
+
258
+ Restore the project and provision the role first. With its services stopped,
259
+ extract the verified archive at the project root, confirm the profile symlink
260
+ targets the restored runtime, then enable the services.
261
+
262
+ ## Retire an agent (preserves runtime by default)
263
+
264
+ Retirement stops external behavior and detaches the profile while preserving
265
+ the role directory and every runtime byte. Do not use a profile command whose
266
+ deletion behavior is unknown.
267
+
268
+ ```bash
269
+ PROJECT=/absolute/path/to/project
270
+ ROLE=pm
271
+ AGENT=bloodbank-pm
272
+ RUNTIME="$PROJECT/agents/hermes/$ROLE/runtime"
273
+ PROFILE="$HOME/.hermes/profiles/$AGENT"
274
+
275
+ systemctl --user disable --now "hermes-${AGENT}-gateway.service"
276
+ systemctl --user disable --now "hermes-${AGENT}-heartbeat.timer"
277
+
278
+ # Detach only the expected symlink. Never follow it into the runtime.
279
+ test -L "$PROFILE"
280
+ test "$(readlink -f -- "$PROFILE")" = "$(readlink -f -- "$RUNTIME")"
281
+ unlink -- "$PROFILE"
282
+
283
+ # Archive the Plane project and retire Telegram/Slack identities through their
284
+ # administrative UIs, then remove the fleet registry entry under its lock.
285
+ # The runtime directory remains in place.
286
+ test -d "$RUNTIME"
287
+ ```
288
+
289
+ ### Runtime retention after retirement
290
+
291
+ Profile and service retirement always preserves the local runtime. This
292
+ release intentionally provides no automated runtime purge, and this operations
293
+ guide supplies no deletion recipe.
294
+
295
+ Any purge is a separate future operator-retention process. It requires a
296
+ separately reviewed, path-safe tool that canonicalizes both the repository root
297
+ and role path, refuses ambiguous or linked targets, and verifies that the
298
+ backup archive contains the expected runtime members before it can remove any
299
+ data. Until such a tool is reviewed and shipped, preserve the runtime.
300
+
301
+ Removing the tracked role scaffold is a different project change and is not
302
+ part of profile or service retirement.
303
+
212
304
  ## Troubleshooting
213
305
 
214
306
  ### Gateway service fails immediately
@@ -216,22 +308,28 @@ rm -rf agents/hermes/<role>
216
308
  - `journalctl --user -u hermes-<agent>-gateway.service`
217
309
  - If "all configured messaging platforms failed to connect" — Telegram step wasn't run yet. Run `.scripts/30-telegram.sh` first.
218
310
 
219
- ### Consumer not seeing events
220
- - Verify NATS is up: `docker compose -f ~/code/33GOD/bloodbank/compose/docker-compose.yml ps`
221
- - Tail consumer: `journalctl --user -fu hermes-<agent>-consumer.service`
222
- - Make sure something is publishing canonical repo events such as
223
- `bloodbank.evt.v1.repo.issue.updated` with `data.repo = <repo>`
311
+ ### Bloodbank command not reaching an agent
312
+ - Confirm the fleet registry entry has `bloodbank.gateway_scope: fleet`
313
+ - Confirm its `bloodbank.target_agent_id` exactly matches the command's
314
+ `data.target_agent_id`
315
+ - Inspect the fleet-shared Bloodbank gateway; there is intentionally no
316
+ `hermes-<agent>-consumer.service` or runtime inbox to repair
317
+
318
+ ### Runtime changes are not appearing in Hindsight or backups
224
319
 
225
- ### Heartbeat not checkpointing (runtime not pushing)
226
- The checkpoint runs inside the heartbeat tick (after the board-reconciliation
227
- sentinel pass), gated to at most once an hour.
320
+ Pure-local runtime changes are not synchronized by the heartbeat.
228
321
  - Look at the most recent heartbeat log: `tail <role>/runtime/logs/heartbeat.log`
229
- - Verify the submodule's git remote is reachable: `cd ...runtime && git push origin HEAD`
230
- - If LFS items fail to push: `git lfs push origin HEAD --all`
231
- - Force a checkpoint out-of-band: `bash agents/hermes/<role>/.scripts/checkpoint.sh`
322
+ - Verify the configured filesystem backup includes the exact runtime path and
323
+ successfully restore-test its latest snapshot.
324
+ - Query Hindsight separately for the agent bank; only events already written
325
+ there are recoverable from Hindsight.
326
+ - Check the secret manager separately for the profile credentials you chose to
327
+ store there.
232
328
 
233
329
  ### Profile dir contains nested `profiles/profiles/...`
234
- - 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.
330
+ - That was a `--clone-all` bug; current provisioning uses `--clone`. Preserve
331
+ the profile and runtime, inspect the unexpected nesting, and move only the
332
+ confirmed redundant entries to a quarantine directory for review.
235
333
 
236
334
  ### `hermes` launcher complains about HERMES_BIN
237
335
  - 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`.