System Sesji AI i Mechanizm Retry
Przegląd
System zarządza sesjami AI providerów aby zapewnić ciągłość kontekstu między subtaskami oraz automatycznie ponawia nieudane subtaski (2 próby) aby chronić przed przejściowymi błędami.
Sesje AI Providerów
Kontekst i Cel
Całość działa na ciągłych sesjach z AI providerami:
- Możesz otrzymać zadanie "dodaj dark mode"
- Następnie "zmień to na niebieski"
- Potem "zmień to na zielony ciemny"
Wszystko powinno być robione w jednej sesji AI providera, aby zachować pełen kontekst konwersacji.
Wspierane Providery
System wspiera następujące providery AI:
| Provider | CLI Tool | Session Format | Status |
|---|---|---|---|
claude | Claude Code | claude-session-{id} | ✅ Zaimplementowane |
codex | Codex CLI | codex-session-{id} | ✅ Zaimplementowane |
gemini | Gemini CLI | gemini-session-{id} | ✅ Zaimplementowane |
mock | Mock (testing) | mock_{random} | ✅ Zaimplementowane |
Struktura Sesji w task.json
Każdy task przechowuje session ID dla wszystkich providerów w sekcji ai.sessions:
{
"task_id": "DEV-7315",
"ai": {
"provider": "claude",
"model": "claude-sonnet-4",
"sessions": {
"claude": null, // id sesji w Claude Code
"codex": null, // id sesji w Codex
"gemini": null // id sesji w Gemini CLI
}
}
}Po utworzeniu pierwszej sesji:
{
"ai": {
"provider": "claude",
"sessions": {
"claude": "claude-session-a1b2c3d4e5f6",
"codex": null,
"gemini": null
}
}
}Lifecycle Sesji
┌─────────────────────────────────────────────────────┐
│ 1. TASK STARTED │
│ - task.json zawiera ai.sessions (wszystkie null) │
│ - Wybór providera z ai.provider │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. PIERWSZY SUBTASK │
│ - Odczyt ai.sessions[provider] z task.json │
│ - Wartość: null │
│ - Uruchomienie CLI bez --session │
│ - Provider tworzy nową sesję │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 3. EKSTRAKCJA SESSION ID │
│ - Odczyt output z CLI │
│ - Parsing session ID (różny format per provider) │
│ - Zapis do task.json: ai.sessions[provider] │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 4. KOLEJNE SUBTASKI │
│ - Odczyt ai.sessions[provider] z task.json │
│ - Wartość: "claude-session-a1b2c3d4e5f6" │
│ - Uruchomienie CLI z --session {SESSION_ID} │
│ - Kontynuacja w tej samej sesji │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 5. ZMIANA PROVIDERA (opcjonalnie) │
│ - Subtask z innym ai.provider (np. codex) │
│ - Odczyt ai.sessions[codex] z task.json │
│ - Wartość: null → tworzy nową sesję │
│ - Zapis session ID do ai.sessions[codex] │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 6. TASK COMPLETED │
│ - Wszystkie session ID zapisane w task.json │
│ - Pełna historia kontekstu zachowana │
└─────────────────────────────────────────────────────┘Implementacja w orchestrator_team.yaml
Lokalizacja: dags/orchestrator_team.yaml:865-942
Krok 1: Odczyt session ID z task.json
# Przed wykonaniem subtaska
CURRENT_PROVIDER=$(jq -r '.ai.provider // "claude"' "$TASK_JSON")
SESSION_ID=$(jq -r ".ai.sessions.${CURRENT_PROVIDER} // null" "$TASK_JSON")
if [ "$SESSION_ID" = "null" ] || [ -z "$SESSION_ID" ]; then
SESSION_ID=""
echo "🆕 Starting new session for provider: $CURRENT_PROVIDER"
else
echo "🔄 Resuming existing session: $SESSION_ID"
fiKrok 2: Przekazanie do Docker
docker compose run \
-e SESSION_ID="$SESSION_ID" \
-e AI_PROVIDER="$CURRENT_PROVIDER" \
worker \
bash -c "..."Krok 3: Użycie w CLI (wewnątrz Docker)
# Claude Code
if [ -n "$SESSION_ID" ]; then
claude --session "$SESSION_ID" "$COMMAND"
else
claude "$COMMAND"
fi
# Codex CLI
if [ -n "$SESSION_ID" ]; then
codex --session "$SESSION_ID" "$COMMAND"
else
codex "$COMMAND"
fi
# Gemini CLI
if [ -n "$SESSION_ID" ]; then
gemini --session "$SESSION_ID" "$COMMAND"
else
gemini "$COMMAND"
fiKrok 4: Ekstrakcja nowego session ID z output
# Różny format per provider
case "$AI_PROVIDER" in
claude)
NEW_SESSION_ID=$(grep "Session ID:" output.log | awk '{print $3}')
;;
codex)
NEW_SESSION_ID=$(grep "session:" output.log | cut -d':' -f2 | tr -d ' ')
;;
gemini)
NEW_SESSION_ID=$(grep "session_id=" output.log | cut -d'=' -f2)
;;
mock)
NEW_SESSION_ID="mock_$(date +%s)_$RANDOM"
;;
esacKrok 5: Zapis z powrotem do task.json
if [ -n "$NEW_SESSION_ID" ]; then
jq --arg provider "$AI_PROVIDER" \
--arg session "$NEW_SESSION_ID" \
'.ai.sessions[$provider] = $session' \
"$TASK_JSON" > "$TASK_JSON.tmp"
mv "$TASK_JSON.tmp" "$TASK_JSON"
echo "✅ Session ID saved: $NEW_SESSION_ID"
fiMock Provider (Testing)
System wspiera mock provider do testów bez wykorzystania prawdziwych API:
Konfiguracja:
{
"ai": {
"provider": "mock",
"model": "mock-model",
"sessions": {
"mock": null
}
}
}Generowanie session ID:
# Format: mock_{timestamp}_{random}
# Przykład: mock_1735742100_12345
NEW_SESSION_ID="mock_$(date +%s)_$RANDOM"Zachowanie:
- Nie wykonuje prawdziwych requestów do AI
- Generuje placeholder output
- Przydatne do testowania orchestration logic
- Zachowuje session ID w task.json jak prawdziwy provider
Mieszanie Providerów
System pozwala na używanie różnych providerów dla różnych subtasków:
Przykład:
// Główny task używa Claude
{
"task_id": "DEV-7315",
"ai": {
"provider": "claude",
"sessions": {
"claude": "claude-session-abc123",
"codex": null,
"gemini": null
}
}
}
// Subtask może użyć Codex dla specific task
{
"task_id": "upload_to_ssh_xyz",
"ai": {
"provider": "codex", // ← inny provider
"sessions": {
"claude": null,
"codex": null, // zostanie utworzona nowa sesja
"gemini": null
}
}
}
// Po wykonaniu subtaska
{
"task_id": "DEV-7315",
"ai": {
"sessions": {
"claude": "claude-session-abc123", // zachowana
"codex": "codex-session-def456", // nowa
"gemini": null
}
}
}Przypadki użycia:
- Upload na SSH → Codex (lepsze w scripting)
- Code implementation → Claude (lepsze w quality)
- Review → Gemini (alternatywna perspektywa)
Uwaga: Tylko użytkownik może decydować o mieszaniu providerów (nie AI).
Session Persistence
Gdzie przechowywane:
- Główny task.json:
tasks/{status}/{TASK_ID}/task.json - Session IDs są persistent across subtasks
- Nie są usuwane po zakończeniu taska (dostępne dla reopen)
Reopen Task:
- Zachowuje wszystkie session IDs
- Kontynuuje w tych samych sesjach
- Pełen kontekst historii zachowany
Follow-up Task:
- NIE kopiuje session IDs z parent
- Tworzy nowe sesje (niezależny kontekst)
Mechanizm Retry dla Subtasków
Cel i Logika
System automatycznie ponawia próby wykonania nieudanych subtasków aby chronić przed:
- Przejściowymi błędami sieciowymi
- Timeout issues
- Temporary API failures
- Race conditions
Maksymalna liczba prób: 2 (1 retry)
Workflow Retry
┌─────────────────────────────────────────────────────┐
│ SUBTASK EXECUTION │
│ Attempt 1/2 │
└─────────────────────────────────────────────────────┘
↓
┌─────────┴─────────┐
│ │
✅ SUCCESS ❌ FAILED
│ │
v v
┌──────────────────┐ ┌──────────────────┐
│ Move to done/ │ │ Check retry count│
│ Continue │ │ │
└──────────────────┘ └──────────────────┘
│
┌─────────────┴─────────────┐
│ │
retry_count = 0 retry_count = 1
│ │
v v
┌──────────────────────┐ ┌──────────────────────┐
│ Create .retry_count │ │ PERMANENT FAILURE │
│ Set value: 1 │ │ │
│ Move back to todo/ │ │ Set FAILURE_DETECTED │
│ Will retry │ │ Skip lower priorities│
└──────────────────────┘ │ Move task to failed/ │
│ └──────────────────────┘
v
┌──────────────────────┐
│ RETRY ATTEMPT │
│ Attempt 2/2 │
└──────────────────────┘
│
┌──────────┴──────────┐
│ │
✅ SUCCESS ❌ FAILED
│ │
v v
┌─────────────────┐ ┌─────────────────┐
│ Delete │ │ PERMANENT FAILURE│
│ .retry_count │ │ (see above) │
│ Move to done/ │ └─────────────────┘
└─────────────────┘Implementacja
Lokalizacja: dags/orchestrator_team.yaml (w sekcji subtask execution)
Sprawdzenie retry count przed wykonaniem:
# Sprawdź czy istnieje plik .retry_count
RETRY_COUNT_FILE="$SUBTASK_DIR/.retry_count"
RETRY_COUNT=0
if [ -f "$RETRY_COUNT_FILE" ]; then
RETRY_COUNT=$(cat "$RETRY_COUNT_FILE")
fi
echo "📊 Retry attempt: $((RETRY_COUNT + 1))/2"Po niepowodzeniu subtaska:
if [ $EXIT_CODE -ne 0 ]; then
if [ $RETRY_COUNT -eq 0 ]; then
# Pierwsza próba niepowodzenia - retry
echo "⚠️ Subtask FAILED with exit code: $EXIT_CODE (attempt 1/2)"
echo "🔄 Retrying subtask..."
# Zapisz retry count
echo "1" > "$SUBTASK_DIR/.retry_count"
# Przenieś z powrotem do todo/
mv "$IN_PROGRESS_DIR/$SUBTASK_DIR" "$TODO_DIR/$SUBTASK_DIR"
echo "📝 Subtask moved back to todo for retry"
else
# Druga próba niepowodzenia - permanent failure
echo "❌ Subtask FAILED with exit code: $EXIT_CODE (attempt 2/2)"
echo "❌ Maximum retry attempts reached - marking as permanent failure"
# Ustaw flagę failure
FAILURE_DETECTED=true
FAILED_PRIORITY="$CURRENT_PRIORITY"
echo "⚠️ Failure detected at priority: $FAILED_PRIORITY"
echo "⚠️ Will skip subsequent priority levels after current priority completes"
# Przenieś do failed/
mv "$IN_PROGRESS_DIR/$SUBTASK_DIR" "$FAILED_DIR/$SUBTASK_DIR"
fi
fiPo sukcesie (usunięcie retry count):
if [ $EXIT_CODE -eq 0 ]; then
echo "✅ Subtask completed successfully"
# Usuń .retry_count jeśli istnieje (był retry)
if [ -f "$SUBTASK_DIR/.retry_count" ]; then
rm "$SUBTASK_DIR/.retry_count"
echo "🗑️ Retry count file removed"
fi
# Przenieś do done/
mv "$IN_PROGRESS_DIR/$SUBTASK_DIR" "$DONE_DIR/$SUBTASK_DIR"
fiStruktura Katalogów
Przed retry:
subtasks/P1/todo/frontend_component_abc123/
├── task.json
├── task.md
└── .retry_count ← zawiera "1"W trakcie retry:
subtasks/P1/in_progress/frontend_component_abc123/
├── task.json
├── task.md
└── .retry_count ← nadal "1"Po sukcesie retry:
subtasks/P1/done/frontend_component_abc123/
├── task.json
└── task.md
# .retry_count został usuniętyPo permanentnym failurze:
subtasks/P1/failed/frontend_component_abc123/
├── task.json
├── task.md
└── .retry_count ← nadal "1" (dla debugowania)Oznaczenia w Logach
Pierwsza próba niepowodzenia:
⚠️ Subtask FAILED with exit code: 1 (attempt 1/2)
🔄 Retrying subtask...
📝 Subtask moved back to todo for retryDruga próba niepowodzenia:
❌ Subtask FAILED with exit code: 1 (attempt 2/2)
❌ Maximum retry attempts reached - marking as permanent failure
⚠️ Failure detected at priority: P1
⚠️ Will skip subsequent priority levels after current priority completesSukces po retry:
📊 Retry attempt: 2/2
✅ Subtask completed successfully
🗑️ Retry count file removedObsługa Niepowodzeń Priorytetów
Gdy subtask z danego poziomu priorytetu failuje permanentnie:
Zasada:
- Subtaski z tego samego poziomu priorytetu są kontynuowane
- Wszystkie subtaski z niższych priorytetów zostają pominięte
- Całe zadanie zostaje przeniesione do
tasks/failed/
Przykład:
P1:
- frontend_abc123: ✅ SUCCESS
- backend_def456: ❌ FAILED (attempt 2/2) ← permanent failure
- test_ghi789: ⏭️ SKIPPED (ten sam P1, więc wykonany)
P2:
- review_jkl012: ⏭️ SKIPPED (niższy priorytet, więc pominięty)
- qa_mno345: ⏭️ SKIPPED
P3:
- docs_pqr678: ⏭️ SKIPPED
Rezultat: Zadanie przeniesione do tasks/failed/Implementacja:
# Flaga failure detection
FAILURE_DETECTED=false
FAILED_PRIORITY=""
# Po permanentnym failurze subtaska
if [ $RETRY_COUNT -eq 1 ]; then
FAILURE_DETECTED=true
FAILED_PRIORITY="$CURRENT_PRIORITY"
fi
# Przed rozpoczęciem kolejnego poziomu priorytetu
if [ "$FAILURE_DETECTED" = true ]; then
echo "⏭️ Skipping priority $NEXT_PRIORITY due to failure at $FAILED_PRIORITY"
break
fiZalety Mechanizmu Retry
Chroni przed:
- ✅ Problemy sieciowe (temporary network issues)
- ✅ API timeouts (gdy provider przeciążony)
- ✅ Race conditions (competing subtasks)
- ✅ Transient Docker issues
Nie zwiększa czasu:
- Zadania które po prostu failują, failują szybko (2 próby)
- Brak długiego czekania na kolejne retry
- "Fail-fast" po drugiej próbie
Nie blokuje:
- Retry nie blokuje innych subtasków
- Subtask wraca do todo/ i czeka na swoją kolej
- Inne subtaski są wykonywane w międzyczasie
Umożliwia debugging:
- Plik
.retry_countpokazuje ile prób było - Failed subtaski zachowują
.retry_countdla analizy - Logi pokazują "attempt X/2" dla każdej próby
Kiedy Retry NIE Pomoże
Retry nie rozwiąże problemów typu:
- ❌ Błędy składniowe w kodzie (syntax errors)
- ❌ Błędy konfiguracji (missing dependencies)
- ❌ Brakujące pliki (file not found)
- ❌ Logic errors w implementacji
W takich przypadkach obie próby zakończą się niepowodzeniem i zadanie zostanie oznaczone jako failed.
Integracja Session Management + Retry
Zachowanie Sesji podczas Retry
Ważne: Session ID jest zachowywany między próbami retry.
Workflow:
Attempt 1:
- Odczyt session ID: "claude-session-abc123"
- Wykonanie w tej samej sesji
- FAILED
- Zapis .retry_count: 1
- Move to todo/
Attempt 2 (retry):
- Odczyt session ID: "claude-session-abc123" ← ta sama sesja!
- Wykonanie w tej samej sesji (kontekst zachowany)
- SUCCESS lub FAILEDKorzyści:
- AI ma pełen kontekst poprzedniej próby
- Może "douczyć się" z błędu
- Zachowana historia konwersacji
Session Management dla Follow-up Tasks
Follow-up task (DEV-7315-FIX-1):
- NIE dziedziczy session ID z parent
- Tworzy nowe sesje dla wszystkich providerów
- Niezależny kontekst (fresh start)
Uzasadnienie:
- Follow-up może być przetwarzany przez innego AI
- Może wymagać odmiennego podejścia
- Unikamy "zanieczyszczenia" kontekstem failowanego taska
Monitoring i Debugging
Sprawdzanie Session Status
# Sprawdź session ID dla taska
jq '.ai.sessions' tasks/in_progress/DEV-7315/task.json
# Output:
# {
# "claude": "claude-session-abc123",
# "codex": null,
# "gemini": null
# }Sprawdzanie Retry Count
# Sprawdź retry count dla subtaska
cat tasks/in_progress/DEV-7315/subtasks/P1/todo/frontend_abc123/.retry_count
# Output: 1Logi Subtasków
# Sprawdź logi wykonania subtaska
cat tasks/in_progress/DEV-7315/artifacts/logs/llm/subtasks/frontend_abc123.log
# Logi zawierają:
# - Attempt number (1/2 or 2/2)
# - Session ID used
# - Exit code
# - Retry decisionFailed Subtasks Analysis
# Lista failed subtasków
ls tasks/in_progress/DEV-7315/subtasks/*/failed/
# Sprawdź retry count dla failed subtaska
cat tasks/in_progress/DEV-7315/subtasks/P1/failed/frontend_abc123/.retry_count
# Output: 1 (oznacza że było retry)Best Practices
1. Session Management
✅ Dobrze:
- Używaj tego samego providera dla całego flow (consistency)
- Mieszaj providery tylko gdy jest to uzasadnione (specjalne zadania)
- Zachowuj session IDs w task.json (nie usuwaj)
❌ Źle:
- Nie zmieniaj providera bez powodu (utrata kontekstu)
- Nie ręczne modyfikuj session IDs w task.json
- Nie kopiuj session IDs między różnymi taskami
2. Retry Strategy
✅ Dobrze:
- Akceptuj że niektóre taski będą failować (to normalne)
- Analizuj failed taski z
.retry_count = 1(były 2 próby) - Sprawdzaj logi obu prób aby zidentyfikować pattern
❌ Źle:
- Nie zwiększaj liczby prób powyżej 2 (nie rozwiąże problemów logic)
- Nie retry subtasków ręcznie (system zrobi to automatycznie)
- Nie usuwaj
.retry_countprzed zakończeniem (potrzebne do debugowania)
3. Provider Selection
Kryteria wyboru:
- Claude: Quality code, complex reasoning, general tasks
- Codex: Scripting, deployment, infrastructure tasks
- Gemini: Alternative perspective, review, analysis
- Mock: Testing orchestration logic
Changelog
v2.0 - Advanced Session & Retry
- ✅ Multi-provider session management (claude, codex, gemini, mock)
- ✅ Session persistence across subtasks
- ✅ 2-attempt retry mechanism z .retry_count
- ✅ Failure detection i priority skipping
- ✅ Session preservation podczas retry
v1.0 - Basic Execution
- ✅ Pojedyncze sesje bez persistence
- ✅ Brak retry (immediate failure)
- ✅ Tylko Claude provider