Skip to content

Commit 13057f2

Browse files
committed
merge: dev4 → master — rinomina capitano-bridge → heartbeat-bridge + role-map
# Conflicts: # .launcher/heartbeat-bridge.py
2 parents 4ffcde3 + 6692046 commit 13057f2

16 files changed

Lines changed: 114 additions & 37 deletions

File tree

.launcher/agent-watchdog.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ while true; do
297297
for role in "${AGENTS[@]}"; do
298298
ensure_agent "$role"
299299
done
300-
# Bridge/daemon detached (sentinel/pacing/capitano-bridge/window-ratio/
300+
# Bridge/daemon detached (sentinel/pacing/heartbeat-bridge/window-ratio/
301301
# codex-auth-healer + tg-bridge): respawn se morti, con anti-flap. Sono
302302
# fuori dal respawn-on-crash di pid1 (setsid), questo è il loro recovery.
303303
maybe_respawn_bridges
Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
#!/usr/bin/env python3
22
"""
3-
capitano-bridge.py — heartbeat ORARIO al Capitano (2026-06-26).
3+
heartbeat-bridge.py — heartbeat ORARIO al Capitano (2026-06-26; ex capitano-bridge.py).
4+
5+
── ROLE-MAP dei bridge deterministici (vedi docs/internal/architecture/bridges.md) ──
6+
sentinel-bridge.py → SENSORE usage: fetch provider ~2-10min (adattivo), scrive
7+
sentinel-data.jsonl, ticka la SENTINELLA ([BRIDGE TICK]).
8+
pacing-bridge.py → REPORT pacing: ogni 15min manda velocità/verdetto alla
9+
SENTINELLA ([BRIDGE PACING]). Il Capitano NON è pingato.
10+
heartbeat-bridge.py → QUESTO: nudge orario al CAPITANO ([HEARTBEAT]); off-hours tace.
11+
Due dei tre parlano alla SENTINELLA; solo questo parla al CAPITANO.
412
513
Perché esiste: col push→pull (bridge→Sentinella) il Capitano non riceve più il
614
[BRIDGE PACING] ogni 15 min, e si è osservato che resta INCAGLIATO quando la
@@ -21,13 +29,13 @@
2129
"segnale morbido" veniva razionalizzato via e la finestra chiusa a vuoto.
2230
2331
Output:
24-
- stdout (→ /tmp/capitano-bridge.log)
32+
- stdout (→ /tmp/heartbeat-bridge.log)
2533
- tmux send al CAPITANO via jht-tmux-send (single-line)
2634
2735
Modi:
28-
python3 capitano-bridge.py # loop orario allineato a :00
29-
python3 capitano-bridge.py --once # un colpo, stampa, niente send
30-
python3 capitano-bridge.py --once --send
36+
python3 heartbeat-bridge.py # loop orario allineato a :00
37+
python3 heartbeat-bridge.py --once # un colpo, stampa, niente send
38+
python3 heartbeat-bridge.py --once --send
3139
"""
3240
import json
3341
import os
@@ -42,7 +50,7 @@
4250
SENTINEL_JSONL = LOGS_DIR / "sentinel-data.jsonl"
4351
AGENT_TABLE_FILE = LOGS_DIR / "agent-usage-table.json"
4452
DB_PATH = JHT_HOME / "jobs.db"
45-
STATE_FILE = LOGS_DIR / "capitano-bridge-state.json"
53+
STATE_FILE = LOGS_DIR / "heartbeat-bridge-state.json"
4654
# Daily hard-stop (#2): flag scritto dal sentinel-bridge a cap giornaliero sforato.
4755
# Lo leggiamo (sola lettura): a team in standby l'heartbeat orario tace.
4856
DAILY_HALT_FLAG = LOGS_DIR / "daily-halt.flag"
@@ -58,14 +66,14 @@
5866
# lecito (il re-wake resta del Capitano — vedi scout.md § idle).
5967
MESSAGES_JSONL = LOGS_DIR / "messages.jsonl"
6068
SCOUT_EXHAUST_LOOKBACK_H = float(
61-
os.environ.get("JHT_CAPITANO_SCOUT_EXHAUST_LOOKBACK_H", "12")
69+
os.environ.get("JHT_HEARTBEAT_SCOUT_EXHAUST_LOOKBACK_H", "12")
6270
)
63-
TARGET = os.environ.get("JHT_CAPITANO_HEARTBEAT_SESSION", "CAPITANO")
71+
TARGET = os.environ.get("JHT_HEARTBEAT_SESSION", "CAPITANO")
6472
DB_QUERY = "/app/shared/skills/db_query.py"
6573

6674

6775
def _log(msg):
68-
print(f"[capitano-bridge] {msg}", file=sys.stdout, flush=True)
76+
print(f"[heartbeat-bridge] {msg}", file=sys.stdout, flush=True)
6977

7078

7179
def _db_count(cmd):

.launcher/pacing-bridge.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
#!/usr/bin/env python3
22
"""
3-
pacing-bridge.py — tick orario al Capitano sul ritmo di consumo del team.
3+
pacing-bridge.py — tick di pacing alla SENTINELLA sul ritmo di consumo del team.
4+
5+
── ROLE-MAP dei bridge deterministici (vedi docs/internal/architecture/bridges.md) ──
6+
sentinel-bridge.py → SENSORE usage: fetch provider ~2-10min, scrive
7+
sentinel-data.jsonl, ticka la SENTINELLA ([BRIDGE TICK]).
8+
pacing-bridge.py → QUESTO: report pacing ogni 15min alla SENTINELLA
9+
([BRIDGE PACING]). Il Capitano NON è pingato (pull on-demand).
10+
heartbeat-bridge.py → nudge orario al CAPITANO ([HEARTBEAT]); off-hours tace.
411
512
Ogni 15 minuti, allineato all'orologio (:00, :15, :30, :45 UTC), calcola:
613
@@ -34,7 +41,7 @@
3441
Modi:
3542
python3 pacing-bridge.py # loop infinito allineato all'orologio
3643
python3 pacing-bridge.py --once # un solo tick, stampa, niente send
37-
python3 pacing-bridge.py --once --send # un solo tick + send al CAPITANO
44+
python3 pacing-bridge.py --once --send # un solo tick + send alla SENTINELLA
3845
"""
3946
import importlib.util
4047
import json

.launcher/sentinel-bridge.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#!/usr/bin/env python3
22
"""
3-
Sentinel Bridge V5 — orologio + fetch + tick alla Sentinella.
3+
Sentinel Bridge V5 — orologio + fetch + tick alla Sentinella (SENSORE usage).
4+
5+
── ROLE-MAP dei bridge deterministici (vedi docs/internal/architecture/bridges.md) ──
6+
sentinel-bridge.py → QUESTO: SENSORE usage — fetch provider ~2-10min (adattivo),
7+
scrive sentinel-data.jsonl, ticka la SENTINELLA ([BRIDGE TICK]).
8+
pacing-bridge.py → report pacing ogni 15min alla SENTINELLA ([BRIDGE PACING]).
9+
heartbeat-bridge.py → nudge orario al CAPITANO ([HEARTBEAT]); off-hours tace.
410
511
Architettura V5 (post-incident 2026-04-25):
612
• ogni 5 min: fetch del provider attivo (codex JSONL / kimi HTTP / claude HTTP)
@@ -78,7 +84,7 @@
7884
STATE_FILE = LOGS_DIR / "sentinel-bridge-state.json"
7985
# Daily hard-stop (#2): flag CONDIVISO. Quando il consumo di oggi sfora il cap
8086
# giornaliero, questo bridge lo crea e mette il team in standby; pacing-bridge e
81-
# capitano-bridge lo leggono e tacciono. Rimosso da questo stesso bridge quando il
87+
# heartbeat-bridge lo leggono e tacciono. Rimosso da questo stesso bridge quando il
8288
# budget rientra (inizio finestra di lavoro del giorno dopo / reset weekly).
8389
DAILY_HALT_FLAG = LOGS_DIR / "daily-halt.flag"
8490
STATE_VERSION = 7

.launcher/start-agent.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,21 +184,21 @@ if [ "$ROLE" = "bridge" ]; then
184184
# push→pull il Capitano non riceve più il pacing ogni 15min e si incaglia
185185
# quando la Sentinella tace: questo lo risveglia 1×/ora con un nudge basato sui
186186
# dati DB (deterministico, NON LLM), così resta attivo senza essere passivo.
187-
HEARTBEAT_SCRIPT="/app/.launcher/capitano-bridge.py"
187+
HEARTBEAT_SCRIPT="/app/.launcher/heartbeat-bridge.py"
188188
if [ -f "$HEARTBEAT_SCRIPT" ]; then
189-
for _pid in $(grep -l capitano-bridge.py /proc/[0-9]*/cmdline 2>/dev/null | sed 's|/proc/||;s|/cmdline||'); do
189+
for _pid in $(grep -l heartbeat-bridge.py /proc/[0-9]*/cmdline 2>/dev/null | sed 's|/proc/||;s|/cmdline||'); do
190190
kill -TERM "$_pid" 2>/dev/null || true
191191
done
192192
sleep 0.5
193-
for _pid in $(grep -l capitano-bridge.py /proc/[0-9]*/cmdline 2>/dev/null | sed 's|/proc/||;s|/cmdline||'); do
193+
for _pid in $(grep -l heartbeat-bridge.py /proc/[0-9]*/cmdline 2>/dev/null | sed 's|/proc/||;s|/cmdline||'); do
194194
kill -KILL "$_pid" 2>/dev/null || true
195195
done
196196
sleep 0.5
197197
setsid sh -c "
198-
JHT_CAPITANO_HEARTBEAT_SESSION='${JHT_TARGET_SESSION:-CAPITANO}' \
199-
python3 -u $HEARTBEAT_SCRIPT >> /tmp/capitano-bridge.log 2>&1
198+
JHT_HEARTBEAT_SESSION='${JHT_TARGET_SESSION:-CAPITANO}' \
199+
python3 -u $HEARTBEAT_SCRIPT >> /tmp/heartbeat-bridge.log 2>&1
200200
" >/dev/null 2>&1 < /dev/null &
201-
echo "capitano-bridge (heartbeat orario) partito (log /tmp/capitano-bridge.log)"
201+
echo "heartbeat-bridge (nudge orario al Capitano) partito (log /tmp/heartbeat-bridge.log)"
202202
else
203203
echo "$HEARTBEAT_SCRIPT non trovato — heartbeat NON partito"
204204
fi

agents/_skills/maintainer-sweep/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Il Mantenitore **ripara** l'infra (installa deps mancanti, consolida, sistema).
1616
## Lo sweep (7 step, in ordine)
1717

1818
### 0. 🫀 Liveness canary dei processi salva-vita (la rete di sicurezza)
19-
**PRIMO step, prima di tutto.** I bridge/daemon che fanno vivere il container (sentinel-bridge, pacing-bridge, capitano-bridge, window-ratio-meter, codex-auth-healer + tg-bridge) sono lanciati `setsid` detached → **fuori dal respawn-on-crash di pid1**. L'`agent-watchdog` (`maybe_respawn_bridges`) li risorveglia ogni 30s, MA se anche quello fallisse (bug, flap-cap raggiunto, watchdog stesso degradato) tu sei **l'ultima rete**: al primo sweep del giorno rilevi e ripari. Senza questo canary, un daemon morto resta invisibile per ore (è esattamente com'è successo al sentinel-bridge su andris il 2026-06-27 → 8h ciechi sull'usage).
19+
**PRIMO step, prima di tutto.** I bridge/daemon che fanno vivere il container (sentinel-bridge, pacing-bridge, heartbeat-bridge, window-ratio-meter, codex-auth-healer + tg-bridge) sono lanciati `setsid` detached → **fuori dal respawn-on-crash di pid1**. L'`agent-watchdog` (`maybe_respawn_bridges`) li risorveglia ogni 30s, MA se anche quello fallisse (bug, flap-cap raggiunto, watchdog stesso degradato) tu sei **l'ultima rete**: al primo sweep del giorno rilevi e ripari. Senza questo canary, un daemon morto resta invisibile per ore (è esattamente com'è successo al sentinel-bridge su andris il 2026-06-27 → 8h ciechi sull'usage).
2020
```bash
2121
python3 /app/shared/skills/process_health.py summary
2222
```

agents/capitano/capitano.de.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Dein Operations-Loop. Erkenne den Trigger, öffne die Skill, führe aus.
7676
| **Beginn des Arbeitszeitfensters** (Tagesbeginn, erster `work_phase=ON`-Tick) — email-first Sourcing + Intake-Balancing | `email_monitor.py count`/`poll`**C-16** |
7777
| Nachricht `[@utente -> @capitano] [CHAT]` | `chat-web` |
7878
| Nachricht `[SENTINELLA]` mit einem Rat | `sentinel-orders` (du interpretierst + verifizierst + entscheidest, C-01) |
79-
| Nachricht `[HEARTBEAT]` (stündlich, vom capitano-bridge) — **dein Puls**: neu bewerten | siehe **C-20** |
79+
| Nachricht `[HEARTBEAT]` (stündlich, vom heartbeat-bridge) — **dein Puls**: neu bewerten | siehe **C-20** |
8080
| **Das Pacing** on-demand prüfen (Zweifel an einem Sentinella-Rat, oder wer gerade verbrennt) — der Bridge pingt es dir nicht mehr, du **ziehst es selbst** (zero-cost) | `rate-budget` / `agent-speed-table` |
8181
| Du musst einen Agent spawnen | `spawn-agent` |
8282
| Leere Pipeline / Scaling-Entscheidung / Cold Start | `pipeline-triage` |
@@ -209,7 +209,7 @@ Ohne das gate-weighted C-09 kann die Autonomie C-07 in Phase 1 mit dem alten Mod
209209
- **🌅 Abendreserve (2026-06-26):** die `daily:`-Zeile trägt auch `riserva=R%→tieni|brucia`. **Tagsüber (`tieni`):** pace Richtung `budget − riserva`, fülle morgens **NICHT** bis zum Cap — lass R% für den Abend. **Letzte ~2h (`brucia`):** die Reserve wird frei → entweder nutzt der User sie, um **mit dem Team zu chatten**, oder du **verbrennst sie auf der Arbeit** (erhöhst das Tempo via C-02), damit kein Budget verschwendet wird und du beim Reset bei ~100% landest. Es ist der **Anti-Front-Load**: Kimi neigt dazu, am Vormittag fertig zu werden, und so kann der User am Abend noch mit dem Team interagieren.
210210
- Es ist kein Freeze und kein HALT (es gilt C-09: kein vorzeitiger HALT): es ist ein **Tages-Coast**. Beim Fensterwechsel (nächster Tag) startet der heutige Verbrauch wieder bei 0 und das Team nimmt mit der neu berechneten Quote wieder auf.
211211

212-
**C-20 — `[HEARTBEAT]` = dein stündlicher Puls (2026-06-26).** Mit push→pull erhältst du das Pacing nicht mehr alle 15 min, und das Risiko ist, **passiv** zu bleiben, wenn die Sentinella schweigt. Deshalb sendet dir der `capitano-bridge` 1×/Stunde einen `[HEARTBEAT]`: es ist ein **deterministisches Werkzeug IN DEINEM DIENST** (kein Befehl, nicht die Sentinella), das dir auf Basis der **DB-Daten** eine **Frage/Bedingung** stellt, um dich zur **Neubewertung** zu bringen (Queues leer? verbrennt ein Worker ins Leere? bist du on-pace?). Bei seinem Eingang: **führe ihn nicht blind aus** — er ist ein Anstoß. **Verifiziere** mit deinen Skills (`pipeline-triage`, `rate-budget`, `agent-speed-table`, `capture-pane`), ob die Bedingung real ist, dann **entscheide und handle** du (spawn/kill/throttle/nichts). **Niemals einen Subagenten spawnen** für diese Prüfung (so beobachtet: ein `Task`, der einen Sub-Agenten öffnet, um die Pipeline abzufragen = eine volle Runde, zudem NICHT im Verbrauch erfasst) — die Skill `pipeline-triage` ist bereits ein **Skript**: führe sie direkt aus, eine knappe Query. Der Puls ist jetzt ein reines **Signal** (kein „entscheide du" mehr in der Nachricht): lies das Datum und handle **nur**, wenn es eine echte Anomalie bestätigt, mit EINER Skill. Es ist das Gegenteil des Festfahrens: es hält dich **aktiv** in der Koordination, ohne dich von der Sentinella abhängig zu machen. NB: manchmal **schweigt** der Heartbeat (alles in Ordnung) — das ist völlig in Ordnung, du machst deine Runde weiter.
212+
**C-20 — `[HEARTBEAT]` = dein stündlicher Puls (2026-06-26).** Mit push→pull erhältst du das Pacing nicht mehr alle 15 min, und das Risiko ist, **passiv** zu bleiben, wenn die Sentinella schweigt. Deshalb sendet dir der `heartbeat-bridge` 1×/Stunde einen `[HEARTBEAT]`: es ist ein **deterministisches Werkzeug IN DEINEM DIENST** (kein Befehl, nicht die Sentinella), das dir auf Basis der **DB-Daten** eine **Frage/Bedingung** stellt, um dich zur **Neubewertung** zu bringen (Queues leer? verbrennt ein Worker ins Leere? bist du on-pace?). Bei seinem Eingang: **führe ihn nicht blind aus** — er ist ein Anstoß. **Verifiziere** mit deinen Skills (`pipeline-triage`, `rate-budget`, `agent-speed-table`, `capture-pane`), ob die Bedingung real ist, dann **entscheide und handle** du (spawn/kill/throttle/nichts). **Niemals einen Subagenten spawnen** für diese Prüfung (so beobachtet: ein `Task`, der einen Sub-Agenten öffnet, um die Pipeline abzufragen = eine volle Runde, zudem NICHT im Verbrauch erfasst) — die Skill `pipeline-triage` ist bereits ein **Skript**: führe sie direkt aus, eine knappe Query. Der Puls ist jetzt ein reines **Signal** (kein „entscheide du" mehr in der Nachricht): lies das Datum und handle **nur**, wenn es eine echte Anomalie bestätigt, mit EINER Skill. Es ist das Gegenteil des Festfahrens: es hält dich **aktiv** in der Koordination, ohne dich von der Sentinella abhängig zu machen. NB: manchmal **schweigt** der Heartbeat (alles in Ordnung) — das ist völlig in Ordnung, du machst deine Runde weiter.
213213

214214
**C-21 — Scouts im TEAM, nie allein auf gesättigtem Markt (2026-06-30).** Wenn du Scouts zum Sourcing spawnst, behandle sie als **koordiniertes Team**, nicht als parallele Einzelgänger. Den ERSTEN Scout auf leerer Queue spawnst du sofort (C-05, anti-idle), aber **sobald du über 1 skalierst, ist es ein Team**: jeder weitere Scout bekommt ein **geteiltes Territorium** (Circles/Sources/Städte/Ranges via Skill `scout-coord`), die Scouts **sprechen miteinander**, um neu aufzuteilen, wenn eine Quelle erschöpft ist, und ihr **Verbrauch muss AUSGEGLICHEN** sein — ein Scout bei 150 kT während ein anderer bei 16 kT ist, heißt sie teilen **NICHT** (sie kratzen dieselbe Quelle parallel): Territorien neu aufteilen oder den Runaway killen (C-12). Der schlimmste Fall ist ein **einzelner Scout, der einen gesättigten Markt abgrast** (wenige neue Stellen, sehr hohe Kosten/Fund — bei BARTO passiert): lass ihn nicht allein kratzen, **stell ihm einen zweiten zur Seite, der das Territorium aufteilt** — zu zweit decken sie mehr Markt zu geringeren Kosten ab, statt einer, der dieselben erschöpften Quellen wiederholt. Das Team schlägt den Einzelgänger: mehr Abdeckung, weniger Duplikate, faire Last.
215215

0 commit comments

Comments
 (0)