Skip to content

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:

ProviderCLI ToolSession FormatStatus
claudeClaude Codeclaude-session-{id}✅ Zaimplementowane
codexCodex CLIcodex-session-{id}✅ Zaimplementowane
geminiGemini CLIgemini-session-{id}✅ Zaimplementowane
mockMock (testing)mock_{random}✅ Zaimplementowane

Struktura Sesji w task.json

Każdy task przechowuje session ID dla wszystkich providerów w sekcji ai.sessions:

json
{
  "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:

json
{
  "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

bash
# 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"
fi

Krok 2: Przekazanie do Docker

bash
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)

bash
# 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"
fi

Krok 4: Ekstrakcja nowego session ID z output

bash
# 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"
    ;;
esac

Krok 5: Zapis z powrotem do task.json

bash
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"
fi

Mock Provider (Testing)

System wspiera mock provider do testów bez wykorzystania prawdziwych API:

Konfiguracja:

json
{
  "ai": {
    "provider": "mock",
    "model": "mock-model",
    "sessions": {
      "mock": null
    }
  }
}

Generowanie session ID:

bash
# 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:

json
// 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:

bash
# 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:

bash
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
fi

Po sukcesie (usunięcie retry count):

bash
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"
fi

Struktura 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ęty

Po 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 retry

Druga 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 completes

Sukces po retry:

📊 Retry attempt: 2/2
✅ Subtask completed successfully
🗑️  Retry count file removed

Obsł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:

bash
# 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
fi

Zalety 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_count pokazuje ile prób było
  • Failed subtaski zachowują .retry_count dla 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 FAILED

Korzyś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

bash
# 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

bash
# Sprawdź retry count dla subtaska
cat tasks/in_progress/DEV-7315/subtasks/P1/todo/frontend_abc123/.retry_count

# Output: 1

Logi Subtasków

bash
# 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 decision

Failed Subtasks Analysis

bash
# 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_count przed 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