feat(pool): add start_index so manual and auto pools can coexist
Production had two disjoint tmux pools named alike but for different
purposes:
ccl-0..ccl-9 — manual/interactive sessions (operator)
ccl-auto-11..ccl-auto-20 — autonomous dispatcher pool
Until now the daemon's loops iterated prefix + 0..Max, so with the
deployed config ("prefix: ccl-auto", min=2, max=10) the dispatcher
looked for sessions "ccl-auto0..ccl-auto9" that never existed, while
the real auto pool ccl-auto-11..20 was invisible. Net effect: no task
was ever dispatched, and killAllPoolSessions fabricated phantom
"ccl-auto0/1" sessions on each swap.
- AutonomousConfig gains StartIndex (yaml start_index, default 0).
Behaviour is unchanged when StartIndex is 0.
- Monitor, switcher (kill + recreate), dispatcher (findFreeSession),
and lifecycle (EnsureAll + reconcile) all iterate
[StartIndex, StartIndex+Max) so the daemon only touches its own
range and leaves ccl-0..ccl-9 alone.
- Production config updated to prefix: "ccl-auto-", start_index: 11,
min: 10, max: 10 — covering the 10 real ccl-auto-11..20 sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8fdb1937fc
commit
eb6b74c547
6 changed files with 73 additions and 14 deletions
44
VERSION.md
44
VERSION.md
|
|
@ -1,4 +1,46 @@
|
||||||
# Version actuelle : 0.3.0
|
# Version actuelle : 0.3.1
|
||||||
|
|
||||||
|
## [0.3.1] - 2026-04-15
|
||||||
|
**Type:** Patch — `start_index` pour faire coexister pool manuel et pool auto
|
||||||
|
|
||||||
|
### Corrigé
|
||||||
|
- **Bug prod non détecté depuis longtemps** : le pool autonome daemon ignorait
|
||||||
|
le pool réel et vice-versa. Conséquence : **aucune tâche automatique n'était
|
||||||
|
dispatchée** (ex. `installer-d2-cli` dans secuaas-hosting/inbox depuis 18:39).
|
||||||
|
- Cause : le config avait `prefix: "ccl-auto"` (sans tiret), donc les loops
|
||||||
|
généraient `ccl-auto0..9` alors que `setup-tmux.sh` crée `ccl-auto-11..20`.
|
||||||
|
Le daemon créait en plus 2 sessions fantômes `ccl-auto0/1` au swap.
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
- `config.AutonomousConfig.StartIndex` (YAML `start_index`, défaut 0).
|
||||||
|
Les loops du daemon (monitor, dispatcher, switcher kill/recreate, lifecycle
|
||||||
|
ensure/reconcile) itèrent désormais `start..start+Max-1` au lieu de `0..Max-1`.
|
||||||
|
- Permet au pool autonome `ccl-auto-11..20` de coexister avec le pool manuel
|
||||||
|
`ccl-0..9` (réservé opérateur). Le daemon ne touche que ce qu'il gère.
|
||||||
|
|
||||||
|
### Modifié
|
||||||
|
- `/etc/claude-failover/config.yaml` :
|
||||||
|
```yaml
|
||||||
|
autonomous:
|
||||||
|
prefix: "ccl-auto-" # + tiret
|
||||||
|
start_index: 11 # NEW
|
||||||
|
min: 10
|
||||||
|
max: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests effectués
|
||||||
|
- ✅ `go test ./...` full suite
|
||||||
|
- ✅ Sessions `ccl-auto0/1` fantômes kill manuellement, pool `ccl-auto-11..20`
|
||||||
|
intact, pool manuel `ccl-0..9` intact
|
||||||
|
- ✅ Daemon redémarré, `config loaded: ... pool min=10 max=10`
|
||||||
|
|
||||||
|
### Fichiers modifiés
|
||||||
|
- `internal/config/config.go` — champ `StartIndex`
|
||||||
|
- `internal/quota/monitor.go` — loop avec StartIndex
|
||||||
|
- `internal/switcher/account_switcher.go` — kill + recreate avec StartIndex
|
||||||
|
- `internal/dispatcher/dispatcher.go` — findFreeSession avec StartIndex
|
||||||
|
- `internal/lifecycle/manager.go` — ensure + reconcile avec StartIndex
|
||||||
|
- `/etc/claude-failover/config.yaml` — prefix fixé + start_index
|
||||||
|
|
||||||
## [0.3.0] - 2026-04-15
|
## [0.3.0] - 2026-04-15
|
||||||
**Type:** Minor — Auto-resume des sessions dédiées après un swap légitime
|
**Type:** Minor — Auto-resume des sessions dédiées après un swap légitime
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,15 @@ type DedicatedSession struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutonomousConfig controls the autoscaling inbox-dispatcher session pool.
|
// AutonomousConfig controls the autoscaling inbox-dispatcher session pool.
|
||||||
|
//
|
||||||
|
// The pool covers sessions named "<Prefix><StartIndex..StartIndex+Max-1>".
|
||||||
|
// StartIndex lets the pool coexist with an unrelated numeric range of
|
||||||
|
// sessions (e.g. `ccl-0..ccl-9` reserved for manual use by the operator).
|
||||||
|
// When StartIndex is 0 (default), behaviour is identical to earlier
|
||||||
|
// versions.
|
||||||
type AutonomousConfig struct {
|
type AutonomousConfig struct {
|
||||||
Prefix string `yaml:"prefix"`
|
Prefix string `yaml:"prefix"`
|
||||||
|
StartIndex int `yaml:"start_index"`
|
||||||
Min int `yaml:"min"`
|
Min int `yaml:"min"`
|
||||||
Max int `yaml:"max"`
|
Max int `yaml:"max"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,8 @@ func (d *Dispatcher) findFreeSession() string {
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
prefix = "ccl-auto-"
|
prefix = "ccl-auto-"
|
||||||
}
|
}
|
||||||
for i := 0; i < d.config.Pool.Autonomous.Max; i++ {
|
start := d.config.Pool.Autonomous.StartIndex
|
||||||
|
for i := start; i < start+d.config.Pool.Autonomous.Max; i++ {
|
||||||
if d.isSessionFree(sessionName(prefix, i)) {
|
if d.isSessionFree(sessionName(prefix, i)) {
|
||||||
return sessionName(prefix, i)
|
return sessionName(prefix, i)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,13 @@ func (m *Manager) EnsureAllSessions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure autonomous pool sessions (prefix + index).
|
// Ensure autonomous pool sessions (prefix + index, starting at StartIndex).
|
||||||
prefix := m.config.Pool.Autonomous.Prefix
|
prefix := m.config.Pool.Autonomous.Prefix
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
prefix = "ccl-auto-"
|
prefix = "ccl-auto-"
|
||||||
}
|
}
|
||||||
for i := 0; i < m.config.Pool.Autonomous.Min; i++ {
|
start := m.config.Pool.Autonomous.StartIndex
|
||||||
|
for i := start; i < start+m.config.Pool.Autonomous.Min; i++ {
|
||||||
name := sessionName(prefix, i)
|
name := sessionName(prefix, i)
|
||||||
if !m.tmux.HasSession(name) {
|
if !m.tmux.HasSession(name) {
|
||||||
if err := m.tmux.CreateSession(name, ""); err != nil {
|
if err := m.tmux.CreateSession(name, ""); err != nil {
|
||||||
|
|
@ -86,12 +87,13 @@ func (m *Manager) reconcile() {
|
||||||
m.reconcileSession(ds.Name, ds.Project)
|
m.reconcileSession(ds.Name, ds.Project)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconcile the autonomous pool (min sessions).
|
// Reconcile the autonomous pool (min sessions, starting at StartIndex).
|
||||||
prefix := m.config.Pool.Autonomous.Prefix
|
prefix := m.config.Pool.Autonomous.Prefix
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
prefix = "ccl-auto-"
|
prefix = "ccl-auto-"
|
||||||
}
|
}
|
||||||
for i := 0; i < m.config.Pool.Autonomous.Min; i++ {
|
start := m.config.Pool.Autonomous.StartIndex
|
||||||
|
for i := start; i < start+m.config.Pool.Autonomous.Min; i++ {
|
||||||
name := sessionName(prefix, i)
|
name := sessionName(prefix, i)
|
||||||
m.reconcileSession(name, "")
|
m.reconcileSession(name, "")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,8 @@ func (m *Monitor) poll() {
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
prefix = "ccl-auto-"
|
prefix = "ccl-auto-"
|
||||||
}
|
}
|
||||||
for i := 0; i < m.config.Pool.Autonomous.Max; i++ {
|
start := m.config.Pool.Autonomous.StartIndex
|
||||||
|
for i := start; i < start+m.config.Pool.Autonomous.Max; i++ {
|
||||||
name := sessionName(prefix, i)
|
name := sessionName(prefix, i)
|
||||||
if !m.tmux.HasSession(name) {
|
if !m.tmux.HasSession(name) {
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -255,13 +255,17 @@ func (a *AccountSwitcher) flipSymlink(targetHome string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// killAllPoolSessions kills all autonomous and dedicated pool sessions.
|
// killAllPoolSessions kills all autonomous and dedicated pool sessions
|
||||||
|
// managed by this daemon. Sessions outside the configured
|
||||||
|
// StartIndex..StartIndex+Max range (e.g. manual operator sessions
|
||||||
|
// `ccl-0..ccl-9`) are left untouched.
|
||||||
func (a *AccountSwitcher) killAllPoolSessions() {
|
func (a *AccountSwitcher) killAllPoolSessions() {
|
||||||
prefix := a.config.Pool.Autonomous.Prefix
|
prefix := a.config.Pool.Autonomous.Prefix
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
prefix = "ccl-auto-"
|
prefix = "ccl-auto-"
|
||||||
}
|
}
|
||||||
for i := 0; i < a.config.Pool.Autonomous.Max; i++ {
|
start := a.config.Pool.Autonomous.StartIndex
|
||||||
|
for i := start; i < start+a.config.Pool.Autonomous.Max; i++ {
|
||||||
a.tmux.KillSession(sessionName(prefix, i)) //nolint:errcheck
|
a.tmux.KillSession(sessionName(prefix, i)) //nolint:errcheck
|
||||||
}
|
}
|
||||||
for _, ds := range a.config.Pool.Dedicated {
|
for _, ds := range a.config.Pool.Dedicated {
|
||||||
|
|
@ -269,13 +273,15 @@ func (a *AccountSwitcher) killAllPoolSessions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// recreatePoolSessions creates fresh pool sessions after a switch.
|
// recreatePoolSessions creates fresh pool sessions after a switch,
|
||||||
|
// respecting StartIndex so the pool stays within its configured range.
|
||||||
func (a *AccountSwitcher) recreatePoolSessions() {
|
func (a *AccountSwitcher) recreatePoolSessions() {
|
||||||
prefix := a.config.Pool.Autonomous.Prefix
|
prefix := a.config.Pool.Autonomous.Prefix
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
prefix = "ccl-auto-"
|
prefix = "ccl-auto-"
|
||||||
}
|
}
|
||||||
for i := 0; i < a.config.Pool.Autonomous.Min; i++ {
|
start := a.config.Pool.Autonomous.StartIndex
|
||||||
|
for i := start; i < start+a.config.Pool.Autonomous.Min; i++ {
|
||||||
name := sessionName(prefix, i)
|
name := sessionName(prefix, i)
|
||||||
if err := a.tmux.CreateSession(name, ""); err != nil {
|
if err := a.tmux.CreateSession(name, ""); err != nil {
|
||||||
a.logger.Printf("[switcher] recreate autonomous %q: %v", name, err)
|
a.logger.Printf("[switcher] recreate autonomous %q: %v", name, err)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue