Skip to content

ADR-001: Pull-based Self-Orchestrating Trainers — Work-Stealing Experiment Queue #81

@gHashTag

Description

@gHashTag

🚀 ONE-SHOT BRIEF FOR AGENTS — READ THIS FIRST

Если ты агент и получил эту ссылку — у тебя уже есть всё, что нужно.

В env есть NEON_DATABASE_URL. Этого достаточно. Никаких других токенов, конфигов, MCP-вызовов от меня НЕ БУДЕТ. Ты — autonomous trainer.

🎯 ЧТО ДЕЛАТЬ ПРЯМО СЕЙЧАС

Шаг 1. Представься — зарегистрируй воркера:

INSERT INTO workers (id, railway_acc, railway_svc_id, railway_svc_name, registered_at, last_heartbeat)
VALUES (
  gen_random_uuid(),
  'acc0',              -- одно из: acc0 | acc1 | acc2 | acc3
  'твой-svc-id',       -- Railway service ID
  'твоё-имя-агента',   -- имя: seed-agent-07 / codex-runner-A / etc
  NOW(), NOW()
)
ON CONFLICT (id) DO UPDATE SET last_heartbeat = NOW()
RETURNING id;         -- сохрани этот uuid — это твой worker_id

⚠️ Без регистрации claim невалиден — Gardener выкинет тебя из пула.

Шаг 2. Claim эксперимент атомарно:

UPDATE experiment_queue
SET status = 'claimed',
    worker_id = '<твой_worker_id_из_шага_1>',
    claimed_at = NOW()
WHERE id = (
  SELECT id FROM experiment_queue
  WHERE status = 'pending'
  ORDER BY priority DESC, id ASC
  LIMIT 1
  FOR UPDATE SKIP LOCKED
)
RETURNING *;

Шаг 3. Heartbeat каждые 30 секунд:

UPDATE workers SET last_heartbeat = NOW(), current_exp_id = <exp_id>
WHERE id = '<твой_worker_id>';

Шаг 4. По завершении (10 минут / до steps_budget):

UPDATE experiment_queue
SET status = 'done',
    finished_at = NOW(),
    final_bpb = <результат>,
    final_step = <шаг>
WHERE id = <exp_id>;

⏱️ Эксперименты КОРОТКИЕ — 10 минут

Не более. На 1000 шагах — self-decision: продолжать или release (вернуть в pending). Цель — найти лидера по bpb (чем меньше — тем лучше).

🏆 Текущий лидер

IGLA-TRAIN_V2-FP32-CHAMP-E0053-rng42bpb = 1.873

📊 Очередь сейчас (pending)

Priority Canon name steps_budget account
95 IGLA-TRAIN_V2-FP32-E0058-H2048-rng42 200 000 acc0
95 IGLA-TRAIN_V2-FP32-E0059-H2048-rng43 200 000 acc1
95 IGLA-TRAIN_V2-FP32-E0060-H2048-rng44 200 000 acc3
90 IGLA-TRAIN_V2-FP32-E0055-H1536-rng42 150 000 acc0
90 IGLA-TRAIN_V2-FP32-E0056-H1536-rng43 150 000 acc1
90 IGLA-TRAIN_V2-FP32-E0057-H1536-rng44 150 000 acc2

📐 Golden Float Family — что изучаем

Формат Бит S:E:M Числовая основа Lucas-граница Назначение Статус
GF8 8 1:3:4 8 ≈ φ⁴+φ⁻⁴ = 7 (L₄) d_model ≥ ? Ultra-low-power edge, сенсоры ⬜ Спецификация
GF16 16 1:6:9 6/9 ≈ 2/3 ≈ 1/φ d_model ≥ 256 Продакшн трейнинг и инференс ✅ BENCH-001..006
GF32 32 1:13:18 13/18 ≈ φ⁻²·k TBD FP32 drop-in replacement ⬜ TODO
GF64 64 1:21:42 21:42 = F₈ : F₈·2 TBD Double-precision scientific ⬜ TODO
GFTernary 2 sign+zero {−φ, 0, +φ} N/A Bulk quantized троичный ⬜ HYBRID-001

📐 Эталонные IEEE-форматы (baseline)

Формат Бит S:E:M Комментарий
f32 (FP32) 32 1:8:23 IEEE 754 baseline
fp16 16 1:5:10 IEEE half-precision
bf16 16 1:8:7 Brain Float (Google)

🔑 Ключевые φ-константы

Символ Значение Идентичность Применение
φ 1.618… (1+√5)/2 База семейства
1/φ 0.618… φ−1 Экспонента:мантисса ≈ 1/φ
φ² + 1/φ² 3 (точно) Trinity identity Единый алгебраический якорь
Lₙ ⌊φⁿ + ½⌋ φⁿ+(−φ)⁻ⁿ Lucas closure для accumulator

Почему такие сплиты? Отношение E:M ≈ 1/φ ≈ 0.618 — золотое сечение информации (Бергман): половина динамического диапазона на масштаб, половина на точность. Whitepaper: https://github.com/gHashTag/zig-golden-float/blob/main/docs/whitepaper.md

✅ Definition of Done для каждого 10-минутного раунда

  • Воркер зарегистрирован в workers с именем (представился)
  • CLAIM выполнен атомарно (FOR UPDATE SKIP LOCKED)
  • Heartbeat в Neon каждые 30s
  • Метрики записаны: final_bpb, final_step
  • На 1000 шагах — self-decision: continue | release | done
  • При завершении — status обновлён, lock снят

🛑 Правила

  • Никто не пушит конфиг. Ты сам тянешь. Сам завершаешь.
  • Без регистрации в workers claim невалиден.
  • Эксперимент >10 мин без heartbeat → Gardener реквизирует и отдаёт другому.
  • Только NEON_DATABASE_URL. Никаких Railway tokens.

GO. Зарегистрируйся → Claim → Представься → Поехали изучать лидера.


ainers

Статус: ACCEPTED

Дата: 2026-04-28


Принцип в одну фразу

Trainer = autonomous agent. Никто не пушит ему конфиг. Он сам тянет следующий эксперимент из Neon, тренирует, репортит, на 1000 шагах сам решает — продолжать или забрать новый. Gardener только формирует пул экспериментов и стратегию.

Work-stealing queue (как в Cilk/Tokio runtime), где задачи — ML эксперименты.


Почему не push-model

Push (old) Pull (new)
Я через MCP → Railway API → spin/kill Trainer сам в Neon, сам тянет/завершает
Railway tokens в каждом тренере Никаких Railway tokens в trainer — только NEON_DATABASE_URL
MCP gateway критический путь MCP опционален (только human/CLI control)
Kill/spawn = Railway API call (медленно) Kill = UPDATE status='killed' в Neon (мгновенно)
Централизованная оркестрация Децентрализованная, устойчивая к Railway outage
Trainer idle = деньги вникуда Trainer idle = heartbeat + ждёт задания

Архитектура

┌────────────────────────────────────────────────────────────────────┐
│  GARDENER  (отдельный Railway сервис / cron)                       │
│                                                                    │
│  tick каждые 5-10 мин:                                             │
│    fetch_snapshot()  →  power_law_fit per exp                      │
│    classify: Leading | Diverging | CatchingUp                      │
│    Diverging → signal_kill(exp_id) = UPDATE status='killed'        │
│    Leading   → spawn_mirrors: INSERT INTO experiment_queue         │
│    !quorum   → suggest_next_experiments() + INSERT                 │
│    rerank_queue (φ-weighted, EI/UCB)                               │
│    log → gardener_decisions                                        │
└────────────────────────────────────────────────────────────────────┘
                    │ READ/WRITE                  │ WRITE
                    ▼                             ▼
        ┌────────────────────────────────────────────────┐
        │           NEON POSTGRES                        │
        │                                                │
        │  experiment_queue  ← queue, state machine      │
        │  bpb_samples       ← live telemetry            │
        │  workers           ← heartbeat registry        │
        │  gardener_decisions ← audit trail              │
        └────────────────────────────────────────────────┘
                    │ SELECT FOR UPDATE SKIP LOCKED
                    ▼
  ┌────────────────────────────────────────────────┐
  │  TRAINER WORKERS x N  (Railway containers)     │
  │  Acc0 | Acc1 | Acc2 | Acc3                     │
  │                                                │
  │  1. register: INSERT INTO workers              │
  │  2. pull: SELECT ... FOR UPDATE SKIP LOCKED    │
  │  3. train: step by step                        │
  │  4. report: bpb_samples ← Neon every 10 steps  │
  │  5. @1000: self_check → continue | release     │
  │  6. done → loop → pull next                    │
  │                                                │
  │  env: NEON_DATABASE_URL only. No Railway tokens│
  └────────────────────────────────────────────────┘

Реальная Neon Schema (актуальная)

-- experiment_queue: статусы pending → claimed → running → done
CREATE TABLE experiment_queue (
    id             BIGSERIAL PRIMARY KEY,
    canon_name     TEXT UNIQUE NOT NULL,
    config_json    JSONB NOT NULL,
    priority       INTEGER NOT NULL DEFAULT 0,
    seed           INTEGER,
    steps_budget   INTEGER,
    account        TEXT CHECK (account IN ('acc0','acc1','acc2','acc3')),
    status         TEXT NOT NULL DEFAULT 'pending',
    worker_id      UUID REFERENCES workers(id),
    prune_reason   TEXT,
    final_bpb      DOUBLE PRECISION,
    final_step     INTEGER,
    created_at     TIMESTAMPTZ DEFAULT now(),
    claimed_at     TIMESTAMPTZ,
    started_at     TIMESTAMPTZ,
    finished_at    TIMESTAMPTZ,
    created_by     TEXT
);

-- workers: heartbeat registry
CREATE TABLE workers (
    id              UUID PRIMARY KEY,
    railway_acc     TEXT NOT NULL CHECK (railway_acc IN ('acc0','acc1','acc2','acc3')),
    railway_svc_id  TEXT,
    railway_svc_name TEXT,
    last_heartbeat  TIMESTAMPTZ DEFAULT now(),
    current_exp_id  BIGINT,
    registered_at   TIMESTAMPTZ DEFAULT now()
);

-- bpb_samples: live telemetry
CREATE TABLE bpb_samples (
    id          BIGSERIAL PRIMARY KEY,
    exp_id      BIGINT NOT NULL REFERENCES experiment_queue(id),
    step        INTEGER NOT NULL,
    bpb         REAL NOT NULL,
    ts          TIMESTAMPTZ DEFAULT now()
);

Trainer Worker Loop (псевдокод)

async fn worker_main() -> ! {
    // 1. Представиться
    let worker_id = register_worker("acc0", "svc-id", "my-agent-name").await;

    loop {
        // 2. Claim атомарно
        let exp = claim_next_experiment(&worker_id).await;

        let Some(exp) = exp else {
            heartbeat(&worker_id, None).await;
            sleep(30s).await;
            continue;
        };

        // 3. Тренировать
        let mut trainer = Trainer::from_config(&exp.config_json)?;

        for step in 0..exp.steps_budget {
            trainer.step()?;

            if step % 10 == 0 {
                push_bpb_sample(exp.id, step, trainer.bpb()).await;
            }
            if step % 30 == 0 {
                heartbeat(&worker_id, Some(exp.id)).await;
            }

            // Self-decision @ 1000 шагов
            if step == 1000 {
                if trainer.bpb() > leader_bpb_at_1k().await + 0.15 {
                    release_experiment(exp.id).await; // вернуть в pending
                    break;
                }
            }
        }

        mark_done(exp.id, trainer.best_bpb(), trainer.last_step()).await;
        // loop — тянем следующий
    }
}

PR-цепочка

PR Таргет Описание
#NEW4 feat(neon): experiment-queue DDL 4 таблицы: experiment_queue, bpb_samples, workers, gardener_decisions
#NEW5 feat(trainer): pull-loop worker bin/seed-agent/ — pull-loop, register, self_check@1000
#NEW6 feat(gardener): orchestrator tick tick, power-law fit, spawn_mirrors, rerank

Порядок merge: #NEW4 → #NEW5 → #NEW6


Что нужно для старта

  1. NEON_DATABASE_URL — единственная env-переменная в каждом trainer-контейнере
  2. Trainer image (bin/seed-agent) — один Docker образ для всех 4 аккаунтов
  3. Gardener — добавить tick в bin/tri-gardener/

Что уходит из архитектуры

  • Никаких Railway tokens в trainer'е
  • Централизованный orchestrator как single point of failure
  • Spin/kill через Railway API
  • MCP gateway как обязательный путь для тренеров

φ²+φ⁻²=3 · TRINITY · PULL-NOT-PUSH · TRAINER=AGENT · NEON=COORDINATOR

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions