Design: Subagent RAM = 0 — visibility bez blokowania kolejki
Status: approved — implements migration 039
Related issue: see GitHub issue created from this doc
Implemented by: migration monitoring/supabase/migrations/039_subagent_zero_ram.sql
Problem
Subagenty spawnowane przez Agent tool działają w chmurze Anthropic, nie na bms-4.
Obecna wartość "subagent":1 w weight_ram_gb (z migration 038) powoduje, że każdy
subagent rezerwuje 1 GB RAM bms-4 w available_ram_gb() — czego w rzeczywistości nie zużywa.
Przy dużej liczbie subagentów blokuje to dispatchera przed uruchamianiem kolejnych workerów.
Cel: subagenty nadal rejestrowane w kolejce (visibility: ile równolegle działa na licencji Anthropic), ale ich RAM = 0 — nie wpływają na budżet RAM bms-4.
Decyzje architektoniczne
| Pytanie | Decyzja | Uzasadnienie |
|---|---|---|
| Czy subagenty liczyć do emergency_max_workers? | NIE | emergency_max_workers chroni zasoby hosta; cloud subagents zużywają 0 zasobów hosta |
| Czy rejestrować subagenty w kolejce? | TAK | widoczność: ile równolegle działa na licencji Anthropic |
| Limit dla v_can_fit gdy slot_ram=0? | p_count bez ograniczeń | brak RAM-gate = brak powodu do limitu; licznik Grafana jest informacyjny |
| emergency_max_workers count w else-branchy? | wyklucz job_type=‘subagent’ | subagent-rows nie powinny zmniejszać dostępnych slotów dla prawdziwych workerów |
Zmiana 1 — Migracja 039_subagent_zero_ram.sql
UPDATE weight_ram_gb (subagent: 1 → 0)
UPDATE dev_r_server_capacity SET
weight_ram_gb = '{"light":3,"heavy":6,"playwright":6,"subagent":0,"orchestrator":12}'
WHERE server_label = 'bms-4';
UPDATE dev_r_server_capacity SET
weight_ram_gb = '{"light":3,"heavy":5,"playwright":5,"subagent":0,"orchestrator":12}'
WHERE server_label = 'vps-i1';available_ram_gb() — bez zmian. Gdy weight='subagent' i wartość=0, SUM dodaje 0. Matematycznie poprawne.
Fix reserve_subagent_slots() — obsługa v_slot_ram=0
CREATE OR REPLACE FUNCTION public.reserve_subagent_slots(
p_parent_session_id TEXT,
p_weight TEXT,
p_count INT,
p_server_label TEXT,
p_parent_issue INT DEFAULT NULL
) RETURNS TABLE(slot_id BIGINT) LANGUAGE plpgsql VOLATILE SECURITY DEFINER AS $$
DECLARE
v_avail_ram NUMERIC;
v_slot_ram NUMERIC;
v_capacity dev_r_server_capacity%ROWTYPE;
v_can_fit INT;
BEGIN
PERFORM pg_advisory_xact_lock(hashtext(p_server_label));
SELECT * INTO v_capacity FROM dev_r_server_capacity WHERE server_label = p_server_label;
v_slot_ram := (v_capacity.weight_ram_gb ->> p_weight)::NUMERIC;
v_avail_ram := available_ram_gb(p_server_label);
IF v_slot_ram = 0 THEN
-- Cloud subagents: zero host resources — no RAM gate, no emergency_max cap
v_can_fit := p_count;
ELSE
v_can_fit := LEAST(
p_count,
GREATEST(0, floor(v_avail_ram / v_slot_ram)::INT),
GREATEST(0, v_capacity.emergency_max_workers - (
-- Exclude cloud subagents (job_type='subagent') — they don't consume host resources
SELECT COUNT(*) FROM dev_r_worker_queue
WHERE server_node = p_server_label
AND status IN ('claimed','running')
AND job_type != 'subagent'
))
);
END IF;
IF v_can_fit = 0 THEN RETURN; END IF;
RETURN QUERY
INSERT INTO dev_r_worker_queue
(job_type, weight, status, server_node, parent_session_id, parent_issue_number,
priority, queued_at, claimed_at, started_at)
SELECT
'subagent', p_weight, 'running', p_server_label, p_parent_session_id, p_parent_issue,
50, NOW(), NOW(), NOW()
FROM generate_series(1, v_can_fit)
RETURNING id;
END;
$$;Fix max_subagents_for_weight() — CASE gdy dzielnik=0
-- p_weight ignored when subagent weight_ram_gb=0; returns emergency_max_workers as concurrency cap
CREATE OR REPLACE FUNCTION public.max_subagents_for_weight(p_server_label TEXT, p_weight TEXT)
RETURNS INT LANGUAGE sql STABLE SECURITY DEFINER AS $$
SELECT CASE
WHEN (s.weight_ram_gb ->> 'subagent')::NUMERIC = 0
THEN s.emergency_max_workers
ELSE GREATEST(0, floor(
((s.os_ram_gb - s.reserved_ram_gb) * 0.80
- (s.weight_ram_gb ->> p_weight)::NUMERIC)
/ (s.weight_ram_gb ->> 'subagent')::NUMERIC
)::INT)
END
FROM dev_r_server_capacity s
WHERE s.server_label = p_server_label
$$;DOWN block
-- DOWN (przywraca stan migration 038):
-- UPDATE dev_r_server_capacity SET
-- weight_ram_gb = '{"light":3,"heavy":6,"playwright":6,"subagent":1,"orchestrator":12}'
-- WHERE server_label = 'bms-4';
-- UPDATE dev_r_server_capacity SET
-- weight_ram_gb = '{"light":3,"heavy":5,"playwright":5,"subagent":1,"orchestrator":12}'
-- WHERE server_label = 'vps-i1';
-- (restore reserve_subagent_slots and max_subagents_for_weight to migration 038 versions)Zmiana 2 — worker-issue.md Step 0b
Usunąć:
- blok exit-4 gdy
slots.Count < PLANNED_SUBAGENTS - ghost-slot cleanup (DELETE przed exit-4)
Zostaje:
max_subagents_for_weight()jako soft cap advisory (nie blokada)reserve_subagent_slots()dla rejestracji wierszy widoczności$SLOT_IDSprzekazywane do Step Final dlacomplete_subagent_slot
Pseudokod po zmianie:
$softCap = max_subagents_for_weight($SERVER_LABEL, $WEIGHT) # 12 gdy subagent=0
$PLANNED = [Math]::Min($PLANNED_SUBAGENTS, $softCap)
$slots = reserve_subagent_slots(SESSION_ID, 'subagent', $PLANNED, $SERVER_LABEL, $ISSUE_NUMBER)
$SLOT_IDS = $slots | ForEach-Object { $_.slot_id }
# spawn $slots.Count agentów — brak exit-4Zmiana 3 — scripts/queue-dispatcher-loop.py
Wyłącznie komunikat exit-4 (~L392):
# Było:
"error_message": f"Re-queued as {next_weight} (needs subagents)"
# Jest:
"error_message": f"Re-queued as {next_weight} (worker self-assessment: needs heavier weight)"Struktura exit-4 (escalation chain) bez zmian — nadal potrzebna dla własnej wagi workera.
Zmiana 4 — Grafana panel servers-overview.json
Nowy stat panel “Cloud subagents (Anthropic)”:
SELECT COUNT(*) AS value
FROM dev_r_worker_queue
WHERE job_type = 'subagent' AND status = 'running';Datasource: Supabase PostgreSQL (grafana_readonly — ma SELECT na dev_r_worker_queue z migration 026).
Dodać jako nowy element panels[], skopiować strukturę istniejącego stat panelu.
Weryfikacja
SELECT available_ram_gb('bms-4')z 10 wierszami subagent → ta sama wartość co bez nich (0×10=0)SELECT * FROM reserve_subagent_slots('s1','subagent',10,'bms-4')→ zwraca 10 wierszy nawet przy pełnym RAMSELECT max_subagents_for_weight('bms-4','orchestrator')→12(emergency_max_workers)- Worker Step 0b PARALLEL-5 → rezerwuje 5 slotów, spawnia 5, brak exit-4
- Grafana panel pokazuje > 0 gdy worker aktywnie spawnuje subagenty
SELECT available_ram_gb('bms-4')z 3 light workers + 10 subagents →22.4 - 9 = 13.4(subagents nie liczą)