@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.
- package/.mise/scripts/link-agentfiles.sh +9 -0
- package/dist/index.js +1492 -358
- package/dist/mcp-server.js +2391 -290
- package/package.json +5 -2
- package/templates/commonproject/.mise/scripts/create-plane-project.sh +26 -0
- package/templates/commonproject/copier.yml +2 -0
- package/templates/commonproject/template/.agents/hooks/README.md +151 -0
- package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +63 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-journal-write.sh +327 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-recall.sh +180 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-retain.sh +74 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-session-end.sh +57 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-bank.sh +57 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-journal.sh +48 -0
- package/templates/commonproject/template/.agents/hooks/hooks.master.json +97 -0
- package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +30 -0
- package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +51 -0
- package/templates/commonproject/template/.agents/hooks/reminder-for-skill-check.sh +20 -0
- package/templates/commonproject/template/.agents/hooks/sync.py +618 -0
- package/templates/commonproject/template/.agents/local.example.json +15 -0
- package/templates/commonproject/template/.mise/scripts/hindsight-setup.sh +62 -0
- package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +110 -0
- package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +45 -0
- package/templates/commonproject/template/mise.toml.jinja +42 -0
- package/templates/hermes-agent/.codegraph/daemon.pid +6 -0
- package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +10 -0
- package/templates/hermes-agent/README.md +16 -14
- package/templates/hermes-agent/copier.yml +7 -25
- package/templates/hermes-agent/docs/architecture.md +16 -18
- package/templates/hermes-agent/docs/fleet-control-plane/README.md +35 -0
- package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +253 -0
- package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +280 -0
- package/templates/hermes-agent/docs/fleet-control-plane/implementation-readiness-report-2026-06-27.md +118 -0
- package/templates/hermes-agent/docs/fleet-control-plane/prd.md +141 -0
- package/templates/hermes-agent/docs/operations.md +17 -19
- package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +17 -13
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/README.md +47 -46
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/architecture.md +39 -38
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/development.md +63 -99
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/providers.md +2 -2
- package/templates/hermes-agent/docs/sentinel.md +18 -0
- package/templates/hermes-agent/install-local.sh +22 -20
- package/templates/hermes-agent/runtime-scaffold/README.md +10 -9
- package/templates/hermes-agent/scripts/backfill-fleet-sot.sh +12 -21
- package/templates/hermes-agent/scripts/fleet-sync.sh +369 -0
- package/templates/hermes-agent/scripts/migrate-unify.sh +252 -0
- package/templates/hermes-agent/scripts/unify-pm.sh +148 -0
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +10 -9
- package/templates/hermes-agent/template/.scripts/01-config.sh +2 -3
- package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -2
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +47 -19
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +71 -27
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +3 -3
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +3 -3
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +48 -43
- package/templates/hermes-agent/template/.scripts/80-registry.sh +2 -2
- package/templates/hermes-agent/template/.scripts/99-summary.sh +3 -4
- package/templates/hermes-agent/template/.scripts/_lib.sh +4 -19
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +36 -0
- package/templates/hermes-agent/template/.scripts/config.example.toml +11 -5
- package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.sh → heartbeat.sh} +84 -31
- package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +1 -1
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +9 -3
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/emit-event.py +5 -5
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-autonomous-review.sh +6 -6
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-close-gate.sh +2 -2
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/autonomous-delegated-review.md +7 -7
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/bloodbank-events.md +3 -3
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/continuous-ticket-orchestration.md +6 -6
- package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.prompt.md.jinja → sentinel.prompt.md.jinja} +5 -4
- package/templates/hermes-agent/template/SOUL.md.jinja +3 -6
- package/templates/hermes-agent/template/hermes.jinja +6 -15
- package/templates/hermes-agent/template/role.yaml.jinja +9 -8
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/agent-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/module-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/workflow-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/agent.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/module.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/workflow.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/analyst.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/architect.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/dev.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/pm.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/quick-flow-solo-dev.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/sm.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/tea.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/tech-writer.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/ux-designer.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/check-implementation-readiness.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/code-review.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/correct-course.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-architecture.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-epics-and-stories.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-dataflow.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-diagram.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-flowchart.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-wireframe.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-product-brief.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-story.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-ux-design.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/dev-story.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/document-project.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/generate-project-context.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/prd.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-dev.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-spec.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/research.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/retrospective.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-planning.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-status.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-atdd.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-automate.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-ci.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-framework.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-nfr.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-design.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-review.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-trace.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-init.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-status.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/agents/brainstorming-coach.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/creative-problem-solver.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/design-thinking-coach.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/innovation-strategist.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/presentation-master.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/storyteller.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/design-thinking.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/innovation-strategy.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/problem-solving.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/storytelling.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/core/agents/bmad-master.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/core/tasks/index-docs.md +0 -9
- package/templates/commonproject/.claude/commands/bmad/core/tasks/shard-doc.md +0 -9
- package/templates/commonproject/.claude/commands/bmad/core/workflows/brainstorming.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/core/workflows/party-mode.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/custom/workflows/ticket-lifecycle.md +0 -5
- package/templates/hermes-agent/docs/scrum-master-sentinel.md +0 -17
- package/templates/hermes-agent/template/.scripts/75-scrum-master.sh +0 -117
- package/templates/hermes-agent/template/.scripts/90-chain-scrum-master.sh +0 -45
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# unify-pm — Phase C (safe subset): move every deployed PM toward the unified
|
|
5
|
+
# single-PM model WITHOUT disrupting running services.
|
|
6
|
+
#
|
|
7
|
+
# Per PM (driven off registry role=pm, so specialized non-pm profiles are
|
|
8
|
+
# naturally skipped):
|
|
9
|
+
# 1. refresh .scripts/checkpoint.sh — applies the secret-scan gate to the live
|
|
10
|
+
# hourly checkpoint (the existing checkpoint.service ExecStarts this file).
|
|
11
|
+
# 2. refresh the <role_dir>/hermes launcher to the runtime-only template
|
|
12
|
+
# (drops the stale profile-fallback; used for interactive `hermes chat`,
|
|
13
|
+
# never by the systemd units).
|
|
14
|
+
# 3. ensure ~/.hermes/profiles/<profile_name> symlinks to ./runtime. This is
|
|
15
|
+
# LOAD-BEARING — hermes resolves `--profile <name>` through it (and recreates
|
|
16
|
+
# it as a fresh standalone dir if missing, disconnecting the agent from its
|
|
17
|
+
# runtime), so it is repaired/created, NEVER dropped.
|
|
18
|
+
#
|
|
19
|
+
# Does NOT touch gateway/consumer/checkpoint units, the registry, or runtimes.
|
|
20
|
+
# The checkpoint→heartbeat unit rename + sentinel install is a separate pass.
|
|
21
|
+
#
|
|
22
|
+
# DRY-RUN by default; --apply performs; --agent <id>... scopes; pjangler-pm first.
|
|
23
|
+
#
|
|
24
|
+
# Usage: unify-pm.sh [--apply] [--agent <pm-id>]...
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
27
|
+
TEMPLATE_DIR="$SCRIPT_DIR/../template"
|
|
28
|
+
CHECKPOINT_SRC="$TEMPLATE_DIR/.scripts/checkpoint.sh"
|
|
29
|
+
WRAPPER_TEMPLATE="$TEMPLATE_DIR/hermes.jinja"
|
|
30
|
+
|
|
31
|
+
HERMES_TEMPLATE_CONFIG="${HERMES_TEMPLATE_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/hermes-agent-template/config.toml}"
|
|
32
|
+
cfg() {
|
|
33
|
+
local key="$1" def="$2"
|
|
34
|
+
{ [[ -f "$HERMES_TEMPLATE_CONFIG" ]] && command -v python3 >/dev/null 2>&1; } || { printf '%s' "$def"; return 0; }
|
|
35
|
+
python3 - "$HERMES_TEMPLATE_CONFIG" "$key" "$def" <<'PYEOF' || printf '%s' "$def"
|
|
36
|
+
import sys, os
|
|
37
|
+
try:
|
|
38
|
+
import tomllib
|
|
39
|
+
except ModuleNotFoundError:
|
|
40
|
+
print(sys.argv[3], end=""); sys.exit(0)
|
|
41
|
+
try:
|
|
42
|
+
cur = tomllib.load(open(sys.argv[1], "rb"))
|
|
43
|
+
except Exception:
|
|
44
|
+
print(sys.argv[3], end=""); sys.exit(0)
|
|
45
|
+
for p in sys.argv[2].split("."):
|
|
46
|
+
if isinstance(cur, dict) and p in cur:
|
|
47
|
+
cur = cur[p]
|
|
48
|
+
else:
|
|
49
|
+
print(sys.argv[3], end=""); sys.exit(0)
|
|
50
|
+
print(os.path.expanduser(str(cur)), end="")
|
|
51
|
+
PYEOF
|
|
52
|
+
return 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
REGISTRY_FILE="${HERMES_FLEET_REGISTRY_FILE:-$(cfg fleet.registry_file "$HOME/.hermes/agents-registry.yaml")}"
|
|
56
|
+
PROFILES_DIR="${HERMES_FLEET_HOME:-$HOME/.hermes}/profiles"
|
|
57
|
+
SYS_DIR="$HOME/.config/systemd/user"
|
|
58
|
+
|
|
59
|
+
APPLY=0
|
|
60
|
+
declare -a ONLY=()
|
|
61
|
+
while [[ $# -gt 0 ]]; do
|
|
62
|
+
case "$1" in
|
|
63
|
+
--apply) APPLY=1 ;;
|
|
64
|
+
--agent) shift; ONLY+=("$1") ;;
|
|
65
|
+
-h|--help) sed -n '3,30p' "$0"; exit 0 ;;
|
|
66
|
+
*) echo "unify-pm: unknown flag $1" >&2; exit 2 ;;
|
|
67
|
+
esac
|
|
68
|
+
shift
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
[[ -f "$REGISTRY_FILE" ]] || { echo "unify-pm: registry not found: $REGISTRY_FILE" >&2; exit 2; }
|
|
72
|
+
[[ -f "$CHECKPOINT_SRC" ]] || { echo "unify-pm: template checkpoint.sh not found: $CHECKPOINT_SRC" >&2; exit 2; }
|
|
73
|
+
[[ -f "$WRAPPER_TEMPLATE" ]] || { echo "unify-pm: wrapper template not found: $WRAPPER_TEMPLATE" >&2; exit 2; }
|
|
74
|
+
|
|
75
|
+
MODE="DRY-RUN"; [[ $APPLY -eq 1 ]] && MODE="APPLY"
|
|
76
|
+
echo "unify-pm — mode: $MODE registry: $REGISTRY_FILE"
|
|
77
|
+
echo "================================================================"
|
|
78
|
+
|
|
79
|
+
# PM rows (pjangler first), TSV: agent_id, role_dir, profile_name
|
|
80
|
+
read_pm() {
|
|
81
|
+
python3 - "$REGISTRY_FILE" <<'PYEOF'
|
|
82
|
+
import sys, yaml
|
|
83
|
+
d = yaml.safe_load(open(sys.argv[1], encoding="utf-8")) or {}
|
|
84
|
+
rows = [(aid, str(a.get("role_dir") or ""), str(a.get("profile_name") or aid))
|
|
85
|
+
for aid, a in (d.get("agents") or {}).items()
|
|
86
|
+
if isinstance(a, dict) and a.get("role") == "pm"]
|
|
87
|
+
rows.sort(key=lambda r: (r[0] != "pjangler-pm", r[0]))
|
|
88
|
+
for r in rows:
|
|
89
|
+
print("\t".join(r))
|
|
90
|
+
PYEOF
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
wanted() { [[ ${#ONLY[@]} -eq 0 ]] && return 0; local a; for a in "${ONLY[@]}"; do [[ "$a" == "$1" ]] && return 0; done; return 1; }
|
|
94
|
+
|
|
95
|
+
CK=0; WR=0; SL=0; SKIP=0
|
|
96
|
+
while IFS=$'\t' read -r aid role_dir profile_name; do
|
|
97
|
+
wanted "$aid" || continue
|
|
98
|
+
echo
|
|
99
|
+
echo ">>> $aid"
|
|
100
|
+
if [[ -z "$role_dir" || ! -d "$role_dir" ]]; then
|
|
101
|
+
echo " role_dir missing: ${role_dir:-<unset>} — skipping"; SKIP=$((SKIP+1)); continue
|
|
102
|
+
fi
|
|
103
|
+
runtime="$role_dir/runtime"
|
|
104
|
+
|
|
105
|
+
# 1. checkpoint.sh secret-gate refresh.
|
|
106
|
+
dst="$role_dir/.scripts/checkpoint.sh"
|
|
107
|
+
if [[ ! -f "$dst" ]] || ! diff -q "$CHECKPOINT_SRC" "$dst" >/dev/null 2>&1; then
|
|
108
|
+
if [[ $APPLY -eq 1 ]]; then
|
|
109
|
+
mkdir -p "$role_dir/.scripts"; cp "$CHECKPOINT_SRC" "$dst"; chmod +x "$dst"
|
|
110
|
+
echo " [do] checkpoint.sh refreshed (secret-gate)"; CK=$((CK+1))
|
|
111
|
+
else
|
|
112
|
+
echo " [dry] checkpoint.sh would be refreshed (secret-gate)"; CK=$((CK+1))
|
|
113
|
+
fi
|
|
114
|
+
else
|
|
115
|
+
echo " checkpoint.sh already current"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# 2. launcher → runtime-only template.
|
|
119
|
+
expected_wrapper="$(sed "s/{{ agent_id }}/$aid/g" "$WRAPPER_TEMPLATE")"
|
|
120
|
+
if [[ ! -f "$role_dir/hermes" ]] || ! diff -q <(printf '%s\n' "$expected_wrapper") "$role_dir/hermes" >/dev/null 2>&1; then
|
|
121
|
+
if [[ $APPLY -eq 1 ]]; then
|
|
122
|
+
printf '%s\n' "$expected_wrapper" > "$role_dir/hermes"; chmod +x "$role_dir/hermes"
|
|
123
|
+
echo " [do] launcher regenerated (runtime-only)"; WR=$((WR+1))
|
|
124
|
+
else
|
|
125
|
+
echo " [dry] launcher would be regenerated (runtime-only)"; WR=$((WR+1))
|
|
126
|
+
fi
|
|
127
|
+
else
|
|
128
|
+
echo " launcher already current"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# 3. ensure the profile symlink points at the runtime (load-bearing; never drop).
|
|
132
|
+
link="$PROFILES_DIR/$profile_name"
|
|
133
|
+
if [[ -L "$link" && "$(readlink -f "$link")" == "$(readlink -f "$runtime")" ]]; then
|
|
134
|
+
echo " profile symlink ok -> runtime"
|
|
135
|
+
elif [[ -e "$link" && ! -L "$link" ]]; then
|
|
136
|
+
echo " profile entry is a REAL dir (MANUAL merge): $link — left untouched"; SKIP=$((SKIP+1))
|
|
137
|
+
else
|
|
138
|
+
if [[ $APPLY -eq 1 ]]; then
|
|
139
|
+
mkdir -p "$PROFILES_DIR"; ln -sfn "$runtime" "$link"; echo " [do] profile symlink set -> runtime"; SL=$((SL+1))
|
|
140
|
+
else
|
|
141
|
+
echo " [dry] profile symlink would be set -> runtime"; SL=$((SL+1))
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
done < <(read_pm)
|
|
145
|
+
|
|
146
|
+
echo
|
|
147
|
+
echo "================================================================"
|
|
148
|
+
echo "unify-pm: checkpoint=$CK launcher=$WR symlink-repair=$SL skipped=$SKIP ($MODE)"
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# {{agent_id}} — runtime
|
|
2
2
|
|
|
3
|
-
This is the
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
recoverable across machines.
|
|
3
|
+
This is the HERMES_HOME for the **{{display_name}}** agent. It is git-tracked
|
|
4
|
+
so memory, SOUL evolution, and conversation history are durable across machines
|
|
5
|
+
and recoverable on failure.
|
|
7
6
|
|
|
8
7
|
## What's in here
|
|
9
8
|
|
|
10
9
|
| Path | Tracked? | Purpose |
|
|
11
10
|
| --- | --- | --- |
|
|
12
|
-
| `
|
|
13
|
-
| `config.yaml` | yes | Local config overrides only; shared defaults come from the fleet default |
|
|
11
|
+
| `config.yaml` | yes | Inference + skill config (cloned from global at provision time) |
|
|
14
12
|
| `SOUL.md` | yes | The agent's personality, evolves over time |
|
|
15
13
|
| `memories/MEMORY.md` | yes | The condensed mental-model summary loaded each session |
|
|
16
14
|
| `memories/USER.md` | yes | The operator's persona (Jarad DeLorenzo, ...) |
|
|
@@ -25,11 +23,14 @@ recoverable across machines.
|
|
|
25
23
|
|
|
26
24
|
## Checkpoint cadence
|
|
27
25
|
|
|
28
|
-
-
|
|
26
|
+
- Heartbeat: systemd `--user` timer `hermes-{{agent_id}}-heartbeat.timer`. Each
|
|
27
|
+
tick reconciles the ticket board (the PM's sentinel pass) and then, gated to
|
|
28
|
+
at most once an hour, checkpoints this runtime.
|
|
29
29
|
- On session end: hermes Stop hook (TODO: hook script in `~/.hermes/hooks/`)
|
|
30
30
|
|
|
31
|
-
The
|
|
32
|
-
`git add -A`, commits only if dirty,
|
|
31
|
+
The heartbeat runner lives in the parent's `.scripts/heartbeat.sh`; it calls
|
|
32
|
+
`.scripts/checkpoint.sh` internally, which `git add -A`, commits only if dirty,
|
|
33
|
+
and pushes to `origin`.
|
|
33
34
|
|
|
34
35
|
## Restoring on a new machine
|
|
35
36
|
|
|
@@ -21,9 +21,8 @@ else
|
|
|
21
21
|
cat > "$CONFIG" <<'TOML'
|
|
22
22
|
# hermes-agent-template config — edit for your environment.
|
|
23
23
|
[fleet]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
hermes_repo = "/home/delorenj/code/hermes-agent"
|
|
24
|
+
hermes_bin = "~/.hermes/hermes-agent/.venv/bin/hermes"
|
|
25
|
+
hermes_repo = "~/.hermes/hermes-agent"
|
|
27
26
|
fleet_env = "~/.hermes/fleet.env"
|
|
28
27
|
registry_file = "~/.hermes/agents-registry.yaml"
|
|
29
28
|
canonical_skills_dir = "/home/delorenj/.agents/skills"
|
|
@@ -15,7 +15,6 @@ if [[ ! -f "$FLEET_ENV" ]]; then
|
|
|
15
15
|
# All generated wrappers and provisioning scripts read this file.
|
|
16
16
|
HERMES_FLEET_BIN=${HERMES_BIN}
|
|
17
17
|
HERMES_FLEET_REPO=${HERMES_AGENT_REPO}
|
|
18
|
-
HERMES_FLEET_HOME=${FLEET_HOME}
|
|
19
18
|
HERMES_FLEET_REGISTRY_FILE=${REGISTRY_FILE}
|
|
20
19
|
HERMES_FLEET_OAUTH_FILE=${HERMES_OAUTH_FILE}
|
|
21
20
|
HERMES_FLEET_CODEX_HOME=${CODEX_HOME}
|
|
@@ -46,7 +45,6 @@ path.write_text("\n".join(lines) + "\n")
|
|
|
46
45
|
PYEOF
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
upsert_fleet_env HERMES_FLEET_HOME "$FLEET_HOME"
|
|
50
48
|
upsert_fleet_env HERMES_FLEET_OAUTH_FILE "$HERMES_OAUTH_FILE"
|
|
51
49
|
upsert_fleet_env HERMES_FLEET_CODEX_HOME "$CODEX_HOME"
|
|
52
50
|
chmod 600 "$FLEET_ENV"
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# Stage the per-agent runtime config/secrets via a TRANSIENT Hermes profile.
|
|
3
|
+
# HERMES_HOME is the ./runtime submodule; this profile dir is a staging area
|
|
4
|
+
# (cred-stripped .env + SOUL) that 20-runtime-repo.sh folds into the runtime and
|
|
5
|
+
# then replaces with a symlink ~/.hermes/profiles/<name> -> ./runtime. That
|
|
6
|
+
# symlink is load-bearing: hermes resolves `--profile <name>` invocations
|
|
7
|
+
# through it (recreating it wrongly if it goes missing).
|
|
4
8
|
# shellcheck source=_lib.sh
|
|
5
9
|
source "$(dirname "$0")/_lib.sh"
|
|
6
10
|
load_role_env
|
|
@@ -8,7 +12,7 @@ load_role_env
|
|
|
8
12
|
already_done 10-hermes-profile && { log "[10] profile already created — skipping"; exit 0; }
|
|
9
13
|
|
|
10
14
|
log "[10] creating hermes profile: $PROFILE_NAME"
|
|
11
|
-
PROFILE_HOME="$
|
|
15
|
+
PROFILE_HOME="$HOME/.hermes/profiles/$PROFILE_NAME"
|
|
12
16
|
|
|
13
17
|
# Set repo path to directory where .git is
|
|
14
18
|
REPO_PATH="$(cd "$ROLE_DIR" && git rev-parse --show-toplevel 2>/dev/null || echo "$ROLE_DIR")"
|
|
@@ -16,21 +20,18 @@ REPO_PATH="$(cd "$ROLE_DIR" && git rev-parse --show-toplevel 2>/dev/null || echo
|
|
|
16
20
|
if [[ -d "$PROFILE_HOME" ]]; then
|
|
17
21
|
log " profile dir already exists; reusing"
|
|
18
22
|
else
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
# --clone (NOT --clone-all): copies config.yaml, .env, SOUL.md only.
|
|
24
|
+
# --clone-all has a recursion bug — it copies the entire ~/.hermes tree,
|
|
25
|
+
# including profiles/ itself, producing nested profiles/<name>/profiles/<name>/...
|
|
26
|
+
# We do explicit skill + plugin + hooks copies below to avoid that.
|
|
27
|
+
"$HERMES_BIN" profile create "$PROFILE_NAME" --clone --no-alias
|
|
21
28
|
fi
|
|
22
29
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
env HERMES_HOME="$FLEET_HOME" "$HERMES_BIN" profile config-inherit \
|
|
27
|
-
"$PROFILE_NAME" --from default
|
|
28
|
-
|
|
29
|
-
# Copy local profile assets from the default profile without copying config or
|
|
30
|
-
# credentials. Only config.yaml inherits; these directories remain local state.
|
|
31
|
-
log " mirroring local skills, plugins, hooks from default profile"
|
|
30
|
+
# Manually copy the inheritable bits that --clone doesn't get.
|
|
31
|
+
# These are content-only dirs; safe to mirror without recursion risk.
|
|
32
|
+
log " mirroring skills, plugins, hooks from default profile"
|
|
32
33
|
for sub in skills plugins hooks cron skins; do
|
|
33
|
-
src="$
|
|
34
|
+
src="$HOME/.hermes/$sub"
|
|
34
35
|
dst="$PROFILE_HOME/$sub"
|
|
35
36
|
if [[ -d "$src" && "$src" != "$PROFILE_HOME"* ]]; then
|
|
36
37
|
mkdir -p "$dst"
|
|
@@ -45,10 +46,15 @@ rm -f "$PROFILE_HOME/gateway.pid" "$PROFILE_HOME/gateway_state.json" \
|
|
|
45
46
|
# Belt-and-suspenders: if a profiles/ dir somehow exists, remove it
|
|
46
47
|
[[ -d "$PROFILE_HOME/profiles" ]] && rm -rf "$PROFILE_HOME/profiles"
|
|
47
48
|
|
|
48
|
-
# Strip messaging-platform credentials from
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
49
|
+
# Strip inherited messaging-platform credentials from the cloned .env.
|
|
50
|
+
# `profile create --clone` copies the DEFAULT profile's .env verbatim — and the
|
|
51
|
+
# default profile is an operator's own agent (e.g. Condaleeza on Slack). Without
|
|
52
|
+
# this, every sub-agent inherits the parent's bot token and would (a) hijack the
|
|
53
|
+
# parent's Slack/Telegram socket if it ever connects, and (b) crash-loop: the
|
|
54
|
+
# gateway treats an inherited-but-unusable platform as "configured", fails to
|
|
55
|
+
# connect it, and exits non-fatal only when ZERO platforms are configured. A
|
|
56
|
+
# sub-agent must establish its OWN identity via the Wire steps (30-telegram etc),
|
|
57
|
+
# never borrow the parent's.
|
|
52
58
|
PROFILE_ENV="$PROFILE_HOME/.env"
|
|
53
59
|
if [[ -f "$PROFILE_ENV" ]]; then
|
|
54
60
|
log " stripping inherited platform credentials from profile .env"
|
|
@@ -73,6 +79,28 @@ PYEOF
|
|
|
73
79
|
chmod 600 "$PROFILE_ENV"
|
|
74
80
|
fi
|
|
75
81
|
|
|
82
|
+
if [[ "$ROLE" == "pm" ]]; then
|
|
83
|
+
VOX_URL_VALUE="${VOX_URL:-$(config_get fleet.vox_url 'https://vox.delo.sh')}"
|
|
84
|
+
if [[ -n "$VOX_URL_VALUE" ]]; then
|
|
85
|
+
log " ensuring VOX_URL is present for PM voice mode"
|
|
86
|
+
python3 - "$PROFILE_ENV" "$VOX_URL_VALUE" <<'PYEOF'
|
|
87
|
+
import pathlib, sys
|
|
88
|
+
|
|
89
|
+
path = pathlib.Path(sys.argv[1])
|
|
90
|
+
vox_url = sys.argv[2]
|
|
91
|
+
lines = path.read_text().splitlines() if path.exists() else []
|
|
92
|
+
for idx, line in enumerate(lines):
|
|
93
|
+
if line.startswith("VOX_URL="):
|
|
94
|
+
lines[idx] = f'VOX_URL="{vox_url}"'
|
|
95
|
+
break
|
|
96
|
+
else:
|
|
97
|
+
lines.append(f'VOX_URL="{vox_url}"')
|
|
98
|
+
path.write_text("\n".join(lines) + "\n")
|
|
99
|
+
PYEOF
|
|
100
|
+
chmod 600 "$PROFILE_ENV"
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
|
|
76
104
|
# Apply role-specific config overrides.
|
|
77
105
|
log " setting terminal.cwd = $REPO_PATH"
|
|
78
106
|
env HERMES_HOME="$PROFILE_HOME" "$HERMES_BIN" config set terminal.cwd "$REPO_PATH"
|
|
@@ -7,7 +7,7 @@ load_role_env
|
|
|
7
7
|
already_done 20-runtime-repo && { log "[20] runtime repo already set up — skipping"; exit 0; }
|
|
8
8
|
[[ "${SKIP_RUNTIME_REPO:-0}" == "1" ]] && { log "[20] runtime repo — SKIPPED (SKIP_RUNTIME_REPO=1)"; mark_done 20-runtime-repo; exit 0; }
|
|
9
9
|
|
|
10
|
-
PROFILE_HOME="$
|
|
10
|
+
PROFILE_HOME="$HOME/.hermes/profiles/$PROFILE_NAME"
|
|
11
11
|
RUNTIME_LOCAL="$ROLE_DIR/runtime"
|
|
12
12
|
GH_OWNER="${RUNTIME_REPO%%/*}"
|
|
13
13
|
GH_NAME="${RUNTIME_REPO##*/}"
|
|
@@ -41,13 +41,13 @@ log " populating scaffold in $TMP"
|
|
|
41
41
|
cp -a "$RUNTIME_SCAFFOLD_DIR/." "$TMP/"
|
|
42
42
|
|
|
43
43
|
# Render scaffold templates with role-specific values
|
|
44
|
-
python3 - "$TMP" "$AGENT_ID" "$REPO" "$ROLE" "$DISPLAY_NAME"
|
|
44
|
+
python3 - "$TMP" "$AGENT_ID" "$REPO" "$ROLE" "$DISPLAY_NAME" <<'PYEOF'
|
|
45
45
|
import sys, pathlib, re
|
|
46
|
-
root, agent_id, repo, role, display
|
|
46
|
+
root, agent_id, repo, role, display = sys.argv[1:6]
|
|
47
47
|
root = pathlib.Path(root)
|
|
48
48
|
mapping = {
|
|
49
49
|
"{{agent_id}}": agent_id, "{{repo}}": repo, "{{role}}": role,
|
|
50
|
-
"{{display_name}}": display,
|
|
50
|
+
"{{display_name}}": display,
|
|
51
51
|
}
|
|
52
52
|
for p in root.rglob("*"):
|
|
53
53
|
if p.is_file() and p.suffix in (".md", ".yaml", ".yml", ".sh", ".py", ".gitignore", ".gitattributes"):
|
|
@@ -59,12 +59,16 @@ for p in root.rglob("*"):
|
|
|
59
59
|
pass
|
|
60
60
|
PYEOF
|
|
61
61
|
|
|
62
|
-
# 3.
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
# 3. Seed the runtime config from the canonical PM config — the fleet's single
|
|
63
|
+
# config source of truth (defaults to the global ~/.hermes/config.yaml). This
|
|
64
|
+
# is a provision-time snapshot; Hermes loads $HERMES_HOME/config.yaml directly
|
|
65
|
+
# (there is no live profile inheritance). Override via config.toml
|
|
66
|
+
# [fleet].canonical_pm_config to share one curated PM config across all repos.
|
|
67
|
+
CANONICAL_PM_CONFIG="$(config_get fleet.canonical_pm_config "$HOME/.hermes/config.yaml")"
|
|
68
|
+
if [[ -f "$CANONICAL_PM_CONFIG" ]]; then
|
|
69
|
+
cp "$CANONICAL_PM_CONFIG" "$TMP/config.yaml"
|
|
70
|
+
fi
|
|
71
|
+
# Copy the project's SOUL.md as the canonical starting personality.
|
|
68
72
|
cp "$ROLE_DIR/SOUL.md" "$TMP/SOUL.md"
|
|
69
73
|
|
|
70
74
|
# 4. Git init + LFS + initial commit + push (skip if remote already has content)
|
|
@@ -118,26 +122,66 @@ else
|
|
|
118
122
|
)
|
|
119
123
|
fi
|
|
120
124
|
|
|
121
|
-
# 6.
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
+
# 6. Fold the staging profile state into the runtime, then symlink the profile
|
|
126
|
+
# name AT the runtime. HERMES_HOME is the runtime submodule, but the profile
|
|
127
|
+
# symlink is LOAD-BEARING: hermes resolves agents launched as
|
|
128
|
+
# `hermes --profile <name>` through ~/.hermes/profiles/<name> (and recreates it
|
|
129
|
+
# as a fresh standalone dir if it's missing, disconnecting the agent from its
|
|
130
|
+
# runtime), so that name MUST resolve to the runtime.
|
|
131
|
+
PROFILE_HOME="$HOME/.hermes/profiles/$PROFILE_NAME"
|
|
125
132
|
if [[ -d "$PROFILE_HOME" && ! -L "$PROFILE_HOME" ]]; then
|
|
126
|
-
log " migrating profile state into the runtime submodule"
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
[[ -f "$PROFILE_HOME/$f" && ! -e "$RUNTIME_LOCAL/$f" ]] \
|
|
132
|
-
&& cp "$PROFILE_HOME/$f" "$RUNTIME_LOCAL/$f"
|
|
133
|
-
done
|
|
134
|
-
for sub in skills plugins hooks cron skins; do
|
|
135
|
-
[[ -d "$PROFILE_HOME/$sub" && ! -e "$RUNTIME_LOCAL/$sub" ]] \
|
|
136
|
-
&& cp -a "$PROFILE_HOME/$sub" "$RUNTIME_LOCAL/$sub"
|
|
133
|
+
log " migrating staging profile state into the runtime submodule"
|
|
134
|
+
# OAuth provider credentials are fleet-shared via HERMES_OAUTH_FILE, so do not
|
|
135
|
+
# clone auth.json/auth.lock into each runtime.
|
|
136
|
+
for f in .env config.yaml; do
|
|
137
|
+
[[ -f "$PROFILE_HOME/$f" && ! -e "$RUNTIME_LOCAL/$f" ]] && cp "$PROFILE_HOME/$f" "$RUNTIME_LOCAL/$f"
|
|
137
138
|
done
|
|
138
139
|
rm -rf "$PROFILE_HOME"
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
fi
|
|
141
|
+
ln -sfn "$RUNTIME_LOCAL" "$PROFILE_HOME"
|
|
142
|
+
log " profile symlink $PROFILE_HOME -> $RUNTIME_LOCAL"
|
|
143
|
+
|
|
144
|
+
# Apply the one genuine per-repo config delta directly to the runtime config.
|
|
145
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set terminal.cwd "$PROJECT_PATH" >/dev/null 2>&1 || true
|
|
146
|
+
|
|
147
|
+
if [[ "$ROLE" == "pm" ]]; then
|
|
148
|
+
VOXXY_PLUGIN_DIR="${VOXXY_PLUGIN_DIR:-$(config_get fleet.voxxy_plugin_dir "$HOME/code/voxxy/plugins/tts/voxxy")}"
|
|
149
|
+
if [[ -d "$VOXXY_PLUGIN_DIR" ]]; then
|
|
150
|
+
mkdir -p "$RUNTIME_LOCAL/plugins/tts"
|
|
151
|
+
ln -sfn "$VOXXY_PLUGIN_DIR" "$RUNTIME_LOCAL/plugins/tts/voxxy"
|
|
152
|
+
log " linked Voxxy plugin into runtime"
|
|
153
|
+
else
|
|
154
|
+
warn " Voxxy plugin dir missing: $VOXXY_PLUGIN_DIR"
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
if [[ -x "$HERMES_BIN" ]]; then
|
|
158
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set plugins.enabled.0 tts/voxxy >/dev/null 2>&1 || true
|
|
159
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set tts.provider voxxy >/dev/null 2>&1 || true
|
|
160
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set tts.voice rick >/dev/null 2>&1 || true
|
|
161
|
+
log " set PM runtime TTS provider -> voxxy"
|
|
162
|
+
else
|
|
163
|
+
warn " Hermes bin missing; skipped PM Voxxy config"
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
if [[ "$ROLE" == "pm" ]]; then
|
|
168
|
+
VOXXY_PLUGIN_DIR="${VOXXY_PLUGIN_DIR:-$(config_get fleet.voxxy_plugin_dir "$HOME/code/voxxy/plugins/tts/voxxy")}"
|
|
169
|
+
if [[ -d "$VOXXY_PLUGIN_DIR" ]]; then
|
|
170
|
+
mkdir -p "$RUNTIME_LOCAL/plugins/tts"
|
|
171
|
+
ln -sfn "$VOXXY_PLUGIN_DIR" "$RUNTIME_LOCAL/plugins/tts/voxxy"
|
|
172
|
+
log " linked Voxxy plugin into runtime"
|
|
173
|
+
else
|
|
174
|
+
warn " Voxxy plugin dir missing: $VOXXY_PLUGIN_DIR"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
if [[ -x "$HERMES_BIN" ]]; then
|
|
178
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set plugins.enabled.0 tts/voxxy >/dev/null 2>&1 || true
|
|
179
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set tts.provider voxxy >/dev/null 2>&1 || true
|
|
180
|
+
env HERMES_HOME="$RUNTIME_LOCAL" "$HERMES_BIN" config set tts.voice rick >/dev/null 2>&1 || true
|
|
181
|
+
log " set PM runtime TTS provider -> voxxy"
|
|
182
|
+
else
|
|
183
|
+
warn " Hermes bin missing; skipped PM Voxxy config"
|
|
184
|
+
fi
|
|
141
185
|
fi
|
|
142
186
|
|
|
143
187
|
rm -rf "$TMP"
|
|
@@ -12,7 +12,7 @@ if [[ "${SKIP_TELEGRAM:-0}" == "1" ]]; then
|
|
|
12
12
|
exit 0
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
-
RUNTIME="$
|
|
15
|
+
RUNTIME="$ROLE_DIR/runtime"
|
|
16
16
|
|
|
17
17
|
cat >&2 <<EOF
|
|
18
18
|
|
|
@@ -59,7 +59,7 @@ if [[ -z "${TELEGRAM_ALLOWED_USERS:-}" ]]; then
|
|
|
59
59
|
read -r -p "Your Telegram user id (allow-list for this bot): " TELEGRAM_ALLOWED_USERS
|
|
60
60
|
fi
|
|
61
61
|
|
|
62
|
-
# Write into
|
|
62
|
+
# Write into runtime/.env (this is HERMES_HOME for the agent)
|
|
63
63
|
ENVF="$RUNTIME/.env"
|
|
64
64
|
python3 - "$ENVF" "$TELEGRAM_BOT_TOKEN" "$TELEGRAM_ALLOWED_USERS" <<'PYEOF'
|
|
65
65
|
import sys, pathlib, re
|
|
@@ -76,7 +76,7 @@ chmod 600 "$ENVF"
|
|
|
76
76
|
# Update role.yaml with the real bot username (in case it differs from the slug)
|
|
77
77
|
yaml_set telegram.bot_username "$bot_username"
|
|
78
78
|
|
|
79
|
-
# Enable telegram toolset for the
|
|
79
|
+
# Enable telegram toolset for the profile
|
|
80
80
|
env HERMES_HOME="$RUNTIME" "$HERMES_BIN" tools enable telegram hermes-telegram 2>/dev/null \
|
|
81
81
|
|| warn " couldn't auto-enable telegram toolset; run: $ROLE_DIR/hermes tools"
|
|
82
82
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Source of truth is the repo-root .project.json `ticket_provider` block
|
|
5
5
|
# (written by the CommonProject base template). The model is:
|
|
6
|
-
# ONE board per repo — the PM owns it,
|
|
7
|
-
# So we never mint a per-agent, role-suffixed board ("Foo PM" /
|
|
8
|
-
#
|
|
6
|
+
# ONE board per repo — the PM owns it, and its heartbeat reconciliation pass
|
|
7
|
+
# watches it. So we never mint a per-agent, role-suffixed board ("Foo PM" /
|
|
8
|
+
# "Foo Sentinel"). Instead:
|
|
9
9
|
# 1. If .project.json already names a board -> BIND to it (no creation).
|
|
10
10
|
# 2. Otherwise (hermes run on a repo with no CommonProject board yet)
|
|
11
11
|
# -> create ONE repo-named board and write it back into .project.json so
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Install systemd --user units: gateway, consumer, and
|
|
2
|
+
# Install systemd --user units: gateway, consumer, and a fused heartbeat timer
|
|
3
|
+
# (board-reconciliation sentinel pass + gated runtime checkpoint, one tick).
|
|
3
4
|
# shellcheck source=_lib.sh
|
|
4
5
|
source "$(dirname "$0")/_lib.sh"
|
|
5
6
|
load_role_env
|
|
@@ -8,23 +9,15 @@ already_done 70-systemd && { log "[70] systemd already installed — skipping";
|
|
|
8
9
|
[[ "${SKIP_SYSTEMD:-0}" == "1" ]] && { log "[70] systemd — SKIPPED"; mark_done 70-systemd; exit 0; }
|
|
9
10
|
|
|
10
11
|
RUNTIME="$ROLE_DIR/runtime"
|
|
11
|
-
|
|
12
|
+
REPO_ROOT="$(project_repo_path)" || REPO_ROOT="$ROLE_DIR"
|
|
12
13
|
SYS_DIR="$HOME/.config/systemd/user"
|
|
13
|
-
mkdir -p "$SYS_DIR"
|
|
14
|
+
mkdir -p "$SYS_DIR" "$RUNTIME/logs"
|
|
14
15
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
RUNTIME_DIR="$(cd "$(dirname "$0")/../runtime" && pwd)"
|
|
21
|
-
cd "$RUNTIME_DIR"
|
|
22
|
-
git add -A
|
|
23
|
-
if git diff --cached --quiet; then exit 0; fi
|
|
24
|
-
git -c commit.gpgsign=false commit -m "checkpoint $(date -Iseconds)" >/dev/null
|
|
25
|
-
git push origin HEAD 2>&1 | tail -1 || true
|
|
26
|
-
CKPT
|
|
27
|
-
chmod +x "$CHECKPOINT_BIN"
|
|
16
|
+
# The heartbeat runner (board-reconciliation sentinel pass + gated checkpoint)
|
|
17
|
+
# and the checkpoint helper both render into the role dir; just ensure they are
|
|
18
|
+
# executable. heartbeat.sh calls checkpoint.sh internally.
|
|
19
|
+
HEARTBEAT_BIN="$ROLE_DIR/.scripts/heartbeat.sh"
|
|
20
|
+
chmod +x "$HEARTBEAT_BIN" "$ROLE_DIR/.scripts/checkpoint.sh" 2>/dev/null || true
|
|
28
21
|
|
|
29
22
|
# Gateway unit
|
|
30
23
|
GW_UNIT="hermes-${AGENT_ID}-gateway.service"
|
|
@@ -36,15 +29,15 @@ Wants=network-online.target
|
|
|
36
29
|
|
|
37
30
|
[Service]
|
|
38
31
|
Type=simple
|
|
39
|
-
Environment=HERMES_HOME=$
|
|
32
|
+
Environment=HERMES_HOME=$RUNTIME
|
|
40
33
|
Environment=HERMES_OAUTH_FILE=$HERMES_OAUTH_FILE
|
|
41
34
|
Environment=CODEX_HOME=$CODEX_HOME
|
|
42
|
-
EnvironmentFile=-$
|
|
35
|
+
EnvironmentFile=-$RUNTIME/.env
|
|
43
36
|
ExecStart=$HERMES_BIN gateway run --replace
|
|
44
37
|
Restart=on-failure
|
|
45
38
|
RestartSec=10
|
|
46
|
-
StandardOutput=append:$
|
|
47
|
-
StandardError=append:$
|
|
39
|
+
StandardOutput=append:$RUNTIME/logs/gateway.systemd.log
|
|
40
|
+
StandardError=append:$RUNTIME/logs/gateway.systemd.log
|
|
48
41
|
|
|
49
42
|
[Install]
|
|
50
43
|
WantedBy=default.target
|
|
@@ -60,57 +53,69 @@ Wants=network-online.target
|
|
|
60
53
|
|
|
61
54
|
[Service]
|
|
62
55
|
Type=simple
|
|
63
|
-
WorkingDirectory=$
|
|
64
|
-
Environment=HERMES_HOME=$
|
|
56
|
+
WorkingDirectory=$RUNTIME
|
|
57
|
+
Environment=HERMES_HOME=$RUNTIME
|
|
65
58
|
Environment=HERMES_OAUTH_FILE=$HERMES_OAUTH_FILE
|
|
66
59
|
Environment=CODEX_HOME=$CODEX_HOME
|
|
67
|
-
EnvironmentFile=-$
|
|
68
|
-
ExecStart=$HERMES_AGENT_REPO/.venv/bin/python $
|
|
60
|
+
EnvironmentFile=-$RUNTIME/.env
|
|
61
|
+
ExecStart=$HERMES_AGENT_REPO/.venv/bin/python $RUNTIME/bloodbank-consumer.py
|
|
69
62
|
Restart=on-failure
|
|
70
63
|
RestartSec=5
|
|
71
|
-
StandardOutput=append:$
|
|
72
|
-
StandardError=append:$
|
|
64
|
+
StandardOutput=append:$RUNTIME/logs/consumer.log
|
|
65
|
+
StandardError=append:$RUNTIME/logs/consumer.log
|
|
73
66
|
|
|
74
67
|
[Install]
|
|
75
68
|
WantedBy=default.target
|
|
76
69
|
UNIT
|
|
77
70
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
# Fused heartbeat: board-reconciliation sentinel pass + gated runtime checkpoint.
|
|
72
|
+
# Frequent ticks (1 min); heartbeat.sh's own cooldown/lock logic rate-limits the
|
|
73
|
+
# full Hermes pass, and the checkpoint is gated to ~hourly inside the runner.
|
|
74
|
+
# The per-agent EnvironmentFiles load ticket-provider creds for the sentinel pass.
|
|
75
|
+
HB_SVC="hermes-${AGENT_ID}-heartbeat.service"
|
|
76
|
+
HB_TIMER="hermes-${AGENT_ID}-heartbeat.timer"
|
|
77
|
+
cat > "$SYS_DIR/$HB_SVC" <<UNIT
|
|
82
78
|
[Unit]
|
|
83
|
-
Description=Hermes
|
|
79
|
+
Description=Hermes Heartbeat (reconcile + checkpoint) — $DISPLAY_NAME
|
|
80
|
+
After=network-online.target
|
|
81
|
+
Wants=network-online.target
|
|
84
82
|
|
|
85
83
|
[Service]
|
|
86
84
|
Type=oneshot
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
WorkingDirectory=$REPO_ROOT
|
|
86
|
+
Environment=HERMES_HOME=$RUNTIME
|
|
87
|
+
Environment=HERMES_OAUTH_FILE=$HERMES_OAUTH_FILE
|
|
88
|
+
Environment=CODEX_HOME=$CODEX_HOME
|
|
89
|
+
EnvironmentFile=-%h/.config/hermes-agent/env
|
|
90
|
+
EnvironmentFile=-%h/.hermes/env
|
|
91
|
+
EnvironmentFile=-%h/.hermes/hermes-agent.env
|
|
92
|
+
EnvironmentFile=-%h/.hermes/${AGENT_ID}.env
|
|
93
|
+
EnvironmentFile=-$RUNTIME/.env
|
|
94
|
+
ExecStart=$HEARTBEAT_BIN
|
|
95
|
+
TimeoutStartSec=45min
|
|
96
|
+
StandardOutput=append:$RUNTIME/logs/heartbeat.log
|
|
97
|
+
StandardError=append:$RUNTIME/logs/heartbeat.log
|
|
90
98
|
UNIT
|
|
91
|
-
cat > "$SYS_DIR/$
|
|
99
|
+
cat > "$SYS_DIR/$HB_TIMER" <<UNIT
|
|
92
100
|
[Unit]
|
|
93
|
-
Description=
|
|
101
|
+
Description=Heartbeat (reconcile + checkpoint) for $AGENT_ID
|
|
94
102
|
|
|
95
103
|
[Timer]
|
|
96
|
-
OnBootSec=
|
|
97
|
-
|
|
98
|
-
Unit=$
|
|
104
|
+
OnBootSec=1min
|
|
105
|
+
OnUnitInactiveSec=1min
|
|
106
|
+
Unit=$HB_SVC
|
|
99
107
|
Persistent=true
|
|
100
108
|
|
|
101
109
|
[Install]
|
|
102
110
|
WantedBy=timers.target
|
|
103
111
|
UNIT
|
|
104
112
|
|
|
105
|
-
mkdir -p "$RUNTIME/logs"
|
|
106
|
-
mkdir -p "$PROFILE_HOME/logs"
|
|
107
|
-
|
|
108
113
|
if systemd_user_available; then
|
|
109
114
|
systemctl --user daemon-reload
|
|
110
115
|
# `enable --now` both enables (persist across login) AND starts the unit now, so a
|
|
111
116
|
# freshly provisioned agent comes up live instead of dormant. Units with missing
|
|
112
117
|
# creds (e.g. a gateway with no Telegram token yet) fail softly via Restart=on-failure.
|
|
113
|
-
for u in "$GW_UNIT" "$CSM_UNIT" "$
|
|
118
|
+
for u in "$GW_UNIT" "$CSM_UNIT" "$HB_TIMER"; do
|
|
114
119
|
systemctl --user enable --now "$u" >/dev/null 2>&1 && log " enabled + started: $u" || warn " failed to enable/start: $u"
|
|
115
120
|
done
|
|
116
121
|
else
|