Skip to main content
A deadlock occurs when a task depends on another task that has permanently failed. The blocked task can never start because its dependency will never complete.
Deadlock chain: Task A failed ← Task B pending (blocked) ← Task C pending (blocked)
Without intervention, tasks B and C would be stuck forever.

Detection

On every tick (5s), Polpo scans for deadlocks:
  1. Find all pending tasks
  2. Check if any depend on failed tasks (with max retries exhausted)
  3. If found → deadlock detected
This triggers the deadlock:detected event with the list of blocked task IDs.

LLM Resolution

Polpo evaluates each deadlocked task with an LLM and decides the best action:

Strategy: Absorb

The blocked task can proceed without the failed dependency. The LLM modifies the task description to account for the missing work.
deadlock:resolved → { action: "absorb", reason: "Task B can build the API with a mock database layer" }
The task description is updated to include context about the failed dependency and what the agent needs to handle differently.

Strategy: Retry

The failed dependency should be retried because it might succeed with a different approach.
deadlock:resolved → { action: "retry", reason: "The database task failed due to a transient error" }
The failed dependency is reset to pending for another attempt.

Unresolvable

The LLM determines the dependency is truly critical and cannot be worked around.
deadlock:unresolvable → { reason: "Task B requires the exact schema from Task A" }
The blocked task is marked as failed.

Configuration

{
  "settings": {
    "maxResolutionAttempts": 2,
    "orchestratorModel": "claude-sonnet-4-6"
  }
}

Events

EventPayloadWhen
deadlock:detected{ taskIds, resolvableCount }Blocked tasks found
deadlock:resolving{ taskId, failedDepId }Attempting resolution
deadlock:resolved{ taskId, failedDepId, action, reason }Successfully resolved
deadlock:unresolvable{ taskId, reason }Cannot resolve

How It Works Internally

  1. Detect: Find pending tasks with failed dependencies
  2. Analyze: Send task descriptions + dependency info to LLM
  3. Decide: LLM returns action (absorb, retry, or fail)
  4. Execute: Apply the decision (modify description, retry dep, or fail task)
  5. Track: Increment resolutionAttempts on the blocked task
  6. Limit: Stop after maxResolutionAttempts to prevent infinite loops
Resolution attempts are tracked per-task via task.resolutionAttempts. Each blocked task gets at most maxResolutionAttempts chances before being permanently failed.