@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.
- package/dist/index.js +2353 -518
- package/dist/mcp-server.js +2331 -494
- package/package.json +14 -3
- package/templates/commonproject/copier.yml +6 -4
- package/templates/commonproject/template/.agents/hooks/README.md +13 -26
- package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +4 -14
- package/templates/commonproject/template/.agents/hooks/sync.py +5 -6
- package/templates/commonproject/template/.agents/local.example.json +2 -8
- package/templates/commonproject/template/.agents/skills.json +6 -0
- package/templates/commonproject/template/.mise/scripts/provision-bmad-skills.py +480 -0
- package/templates/commonproject/template/.mise/scripts/sync-skills.py +449 -0
- package/templates/commonproject/template/mise.toml.jinja +16 -16
- package/templates/commonproject/tests/test_provision_bmad_skills.py +203 -0
- package/templates/commonproject/tests/test_sync_skills_topology.py +120 -0
- package/templates/hermes-agent/README.md +70 -29
- package/templates/hermes-agent/copier.yml +9 -6
- package/templates/hermes-agent/docs/architecture.md +75 -51
- package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +11 -6
- package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +2 -2
- package/templates/hermes-agent/docs/fleet-control-plane/prd.md +2 -3
- package/templates/hermes-agent/docs/operations.md +179 -81
- package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +31 -171
- package/templates/hermes-agent/docs/sentinel/README.md +10 -10
- package/templates/hermes-agent/docs/sentinel/architecture.md +6 -8
- package/templates/hermes-agent/docs/sentinel/development.md +12 -15
- package/templates/hermes-agent/docs/sentinel.md +1 -1
- package/templates/hermes-agent/install-local.sh +41 -4
- package/templates/hermes-agent/runtime-scaffold/README.md +4 -2
- package/templates/hermes-agent/scripts/degit-runtime.sh +192 -0
- package/templates/hermes-agent/scripts/fleet-prune-debris.sh +128 -0
- package/templates/hermes-agent/scripts/fleet-sync.sh +164 -7
- package/templates/hermes-agent/scripts/hermes-runtime-templatize.py +321 -0
- package/templates/hermes-agent/scripts/migrate-profile-scoped-chat-secrets.py +136 -0
- package/templates/hermes-agent/scripts/momo-unify-agent.py +224 -0
- package/templates/hermes-agent/scripts/repair-runtime-checkpoint.sh +7 -119
- package/templates/hermes-agent/template/.gitignore.jinja +5 -2
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +4 -2
- package/templates/hermes-agent/template/.scripts/00-banner.sh +1 -1
- package/templates/hermes-agent/template/.scripts/01-config.sh +3 -0
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +12 -1
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +55 -129
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +276 -22
- package/templates/hermes-agent/template/.scripts/31-slack.sh +362 -0
- package/templates/hermes-agent/template/.scripts/60-bloodbank.sh +8 -66
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +44 -30
- package/templates/hermes-agent/template/.scripts/80-registry.sh +93 -13
- package/templates/hermes-agent/template/.scripts/99-summary.sh +1 -4
- package/templates/hermes-agent/template/.scripts/_lib.sh +66 -1
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +3 -2
- package/templates/hermes-agent/template/.scripts/config.example.toml +10 -10
- package/templates/hermes-agent/template/.scripts/heartbeat.sh +12 -1
- package/templates/hermes-agent/template/.scripts/momo-wip-lock.py +137 -0
- package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +21 -0
- package/templates/hermes-agent/template/SOUL.md.jinja +36 -15
- package/templates/hermes-agent/template/hermes.jinja +6 -3
- package/templates/hermes-agent/template/role.yaml.jinja +24 -13
- package/templates/hermes-agent/tests/test_bloodbank_consumer_contract.py +429 -130
- package/templates/hermes-agent/tests/test_fleet_upgrade_contract.py +176 -0
- package/templates/hermes-agent/tests/test_launcher_chat_secret_isolation.py +85 -0
- package/templates/hermes-agent/tests/test_local_runtime_provisioning.py +130 -0
- package/templates/hermes-agent/tests/test_operator_docs_local_runtime_contract.py +132 -0
- package/templates/hermes-agent/tests/test_profile_env_detach_contract.py +14 -0
- package/templates/hermes-agent/tests/test_profile_scoped_chat_secrets.py +89 -0
- package/templates/hermes-agent/tests/test_repository_privacy_contract.py +32 -0
- package/templates/hermes-agent/tests/test_slack_provisioning.py +344 -0
- package/templates/hermes-agent/tests/test_telegram_provisioning.py +475 -0
- package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +0 -110
- package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +0 -45
- package/templates/hermes-agent/.codegraph/daemon.pid +0 -6
- package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +0 -10
- package/templates/hermes-agent/runtime-scaffold/bloodbank-consumer.py +0 -181
- package/templates/hermes-agent/template/.runtime-scaffold/bloodbank-consumer.py +0 -181
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
##
|
|
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/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
│
|
|
28
|
-
|
|
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
|
|
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
|
|
42
|
-
state — it changes every conversation.
|
|
43
|
-
|
|
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
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
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
|
-
##
|
|
48
|
+
## Durability boundary
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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).
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
99
|
-
|
|
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
|
|
31
|
-
| 30 telegram |
|
|
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 |
|
|
34
|
-
| 70 systemd | Install user units: gateway
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
111
|
-
#
|
|
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.
|
|
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
|
-
#
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 /
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
###
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
|
230
|
-
-
|
|
231
|
-
-
|
|
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;
|
|
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`.
|