Summary
PraisonAI PR #3960 landed three focused correctness fixes in praisonaiagents on 2026-08-15. Each is user-visible (behaviour that changes what an agent does at runtime), and none is currently reflected in the docs. This issue is a single, well-scoped ask so the docs agent can update the right pages in one pass.
Every SDK claim below has been verified against merged source at commit 4c202929717a34838d3eb3f86d2448f21020bd9d on main. All target pages live under docs/features/ or docs/configuration/ — nothing in docs/concepts/ should be touched (per repo AGENTS.md).
Upstream source of truth:
Fix 1 · asequential() failure-cascade parity (async processes)
What changed (verified against praisonaiagents/process/process.py):
The sync sequential() already skips a task whose upstream dependency permanently failed (dependency-cascade check). Before this PR, the async equivalent asequential() — used by astart(), the default async entry point — did not, so a task could try to run against context data its upstream had never produced. Now asequential() mirrors sequential(): it short-circuits failed tasks and cascades a "skipped: upstream failed" outcome down the dependency chain.
User impact. An astart() run with a failing early task now stops the cascade cleanly instead of letting downstream tasks run with empty / missing context. Users who saw async runs producing garbage outputs after an upstream failure will see clean skip outcomes instead.
Docs to update. The one existing page that explicitly names asequential / astart is:
docs/concepts/process.mdx — search results show it mentions asequential in a CardGroup with astart(). DO NOT edit this file (concepts folder is human-approved only). Instead:
- Create
docs/features/async-process-cascade.mdx — a short user-facing page explaining that asequential() / astart() now match the sync path's failure-cascade behaviour. Add a Note linking back from the concepts page in a follow-up human review.
- Alternatively, update an existing async-focused page under
docs/features/ (e.g. docs/features/async.mdx if it exists — check first) to add a ## Failure cascade in asequential()`` section.
Content the page must contain:
- One-sentence lead: "
asequential() now skips downstream tasks whose upstream dependency permanently failed, matching what sequential() already did."
- A tiny agent-centric example: three tasks (A → B → C), A fails, show that with the fix B and C are marked as
"skipped: upstream failed" instead of running against empty context.
- A
sequenceDiagram mermaid showing: astart → task A (fails) → check cascade → skip B → skip C.
- Link back to PR #3960 for provenance.
Fix 2 · Tool executor pool recycling on tool_timeout
What changed (verified against praisonaiagents/agent/tool_execution.py):
Before this PR, when a tool call hung past tool_timeout, future.cancel() could not stop a thread that had already started, so the hung worker permanently occupied one of the pool's 2 worker slots. Two consecutive hangs would deadlock the pool. Now, on timeout, the executor is shutdown(wait=False) and dropped; the next tool call spins up a fresh executor with a fresh worker.
User impact. Any long-running agent that occasionally hits tool_timeout (network stalls, misbehaving APIs, unresponsive shells) is now self-healing at the pool level rather than progressively degrading toward a deadlock. This directly affects users who set aggressive tool_timeout values on flaky third-party tools.
Docs to update:
docs/configuration/tool-config.mdx — the existing tool_timeout documentation. Add a short <Note> (or a "Reliability after a timeout" subsection) explaining pool recycling.
docs/features/yaml-configuration-reference.mdx — the tool_timeout row already documents behaviour extensively. Append one sentence: "When a tool call exceeds the timeout the executor pool is recycled — the next call gets a fresh worker so a hung tool cannot progressively degrade throughput. See PR #3960."
docs/features/concurrency.mdx (if present — check first, this is referenced from yaml-configuration-reference.mdx) — add a short callout to the timeout-precedence table.
Content the note must contain:
- One-sentence lead: "A tool that hangs past
tool_timeout no longer holds its worker slot — the pool is recycled and the next call starts on a fresh worker."
- Explicit statement: "The pool has 2 workers by default; before this fix, two consecutive hangs would deadlock the pool."
- Link back to PR #3960 for provenance.
Fix 3 · Doom-loop NO_PROGRESS recency filter
What changed (verified against praisonaiagents/escalation/doom_loop.py):
Before this PR, _check_no_progress() counted every progress marker for the whole session (there was a literal # Could add timestamp filtering TODO). So one early successful tool call disabled NO_PROGRESS detection for the rest of the run, no matter how long the agent then spun on unproductive work. Now markers are stored as (marker, timestamp) tuples and only markers within the current no-progress window (the boundary is the timestamp of the action immediately preceding the window) are counted.
User impact. NO_PROGRESS doom-loop detection actually fires now on runs where one early success previously suppressed it forever. Long-running autonomous agents will exit stuck states via the recovery path instead of burning the full iteration budget silently.
Important context for the docs agent: There are two separate loop-detection subsystems in this codebase, and they are easy to confuse:
| Subsystem |
Module |
Docs page |
| Result-aware tool-loop detection (per-Agent, always-on, checks tool call fingerprints) |
praisonaiagents/agent/loop_detection.py — LoopDetectionConfig, LoopDetector |
docs/features/doom-loop-detection.mdx (existing) |
Escalation doom-loop detection (session-level, records actions + progress markers, recommends RecoveryAction) |
praisonaiagents/escalation/doom_loop.py — DoomLoopConfig, DoomLoopDetector, DoomLoopType.NO_PROGRESS |
No user-facing page — only auto-generated SDK reference at docs/sdk/reference/praisonaiagents/{classes,modules,functions}/DoomLoop* |
PR #3960's fix is on the escalation subsystem — the one that currently has no user-facing page. docs/features/autonomy-loop.mdx references doom_loop_threshold (via AutonomyConfig) and the doom_loop completion reason, but does not explain the escalation DoomLoopDetector itself.
Docs to update:
- Create
docs/features/escalation-doom-loop.mdx — a new page that finally covers the escalation subsystem end-to-end (the six DoomLoopType values, the RecoveryAction ladder CONTINUE → RETRY_DIFFERENT → ESCALATE_MODEL → REQUEST_HELP → ABORT, the DoomLoopConfig thresholds). This gives Fix 3 a natural home. Place under docs/features/, not docs/concepts/.
- Update
docs/features/autonomy-loop.mdx — the doom_loop_threshold row and doom_loop completion-reason accordion. Add a one-line note that NO_PROGRESS now uses a recency window (early markers no longer suppress it) and link to the new escalation page.
- Cross-link from
docs/features/doom-loop-detection.mdx — add a <Note> at the top disambiguating the two subsystems and linking to the new escalation page, so users landing on the wrong page get redirected.
Content the new escalation page must contain:
- Frontmatter:
title: "Escalation Doom-Loop Detection", sidebarTitle: "Escalation Doom-Loop", icon: "shield-halved".
- Hero mermaid diagram (LR) showing:
Actions/markers → DoomLoopDetector → DoomLoopType → RecoveryAction.
- Quick Start (
<Steps>): (1) construct with defaults, (2) record actions + progress markers, (3) check is_doom_loop() / get_recovery_action().
- Table of the six
DoomLoopType enum values (REPEATED_ACTION, REPEATED_FAILURE, NO_PROGRESS, CIRCULAR_PLAN, RESOURCE_EXHAUSTION, REPEATED_OUTPUT) with what each detects.
- Table of the five
RecoveryAction enum values (CONTINUE, RETRY_DIFFERENT, ESCALATE_MODEL, REQUEST_HELP, ABORT) with when each is chosen (verified from _determine_recovery_action: first attempt → RETRY_DIFFERENT; second attempt with escalate_on_loop=True → ESCALATE_MODEL; then REQUEST_HELP; RESOURCE_EXHAUSTION always → ABORT; _recovery_attempts >= max_recovery_attempts → ABORT).
- Configuration table for
DoomLoopConfig (verified from source):
max_identical_actions: int = 3
max_similar_actions: int = 5
max_consecutive_failures: int = 3
max_no_progress_steps: int = 5
max_time_per_action: float = 60.0
max_total_time: float = 300.0
enable_auto_recovery: bool = True
max_recovery_attempts: int = 2
escalate_on_loop: bool = True
initial_backoff: float = 1.0
backoff_multiplier: float = 2.0
max_backoff: float = 30.0
max_repeated_chunks: int = 8
content_chunk_size: int = 50
- Note that
similarity_threshold: float = 0.85 is kept for backward compatibility only and is not consulted (the docstring in source is explicit about this).
- A "How progress markers work" section covering the PR #3960 recency-filter fix explicitly. Include the concrete example: an early
mark_progress("read config.yaml") no longer suppresses NO_PROGRESS for the rest of the run — markers older than the current window boundary are filtered out.
- Best-practices
<AccordionGroup> (call mark_progress() on real progress, not just any log line; keep max_no_progress_steps small enough to catch stalls quickly).
Placement checklist (per repo AGENTS.md)
Out of scope
- Do not rework
docs/features/doom-loop-detection.mdx beyond adding the disambiguation <Note> — that page correctly documents the result-aware subsystem and its content is accurate as of PR #3005 / #3877.
- Do not modify the auto-generated SDK reference under
docs/sdk/reference/** — those pages are regenerated from source.
- Do not touch the TypeScript (
docs/js/) or Rust (docs/rust/) trees — those are managed by the parity system.
Filed by the docs-triage scheduled routine on 2026-08-16. Verified against upstream commit 4c202929717a34838d3eb3f86d2448f21020bd9d.
Summary
PraisonAI PR #3960 landed three focused correctness fixes in
praisonaiagentson 2026-08-15. Each is user-visible (behaviour that changes what an agent does at runtime), and none is currently reflected in the docs. This issue is a single, well-scoped ask so the docs agent can update the right pages in one pass.Every SDK claim below has been verified against merged source at commit
4c202929717a34838d3eb3f86d2448f21020bd9donmain. All target pages live underdocs/features/ordocs/configuration/— nothing indocs/concepts/should be touched (per repo AGENTS.md).Upstream source of truth:
praisonaiagents/process/process.py,praisonaiagents/agent/tool_execution.py,praisonaiagents/escalation/doom_loop.pyFix 1 ·
asequential()failure-cascade parity (async processes)What changed (verified against
praisonaiagents/process/process.py):The sync
sequential()already skips a task whose upstream dependency permanently failed (dependency-cascade check). Before this PR, the async equivalentasequential()— used byastart(), the default async entry point — did not, so a task could try to run against context data its upstream had never produced. Nowasequential()mirrorssequential(): it short-circuits failed tasks and cascades a "skipped: upstream failed" outcome down the dependency chain.User impact. An
astart()run with a failing early task now stops the cascade cleanly instead of letting downstream tasks run with empty / missing context. Users who saw async runs producing garbage outputs after an upstream failure will see clean skip outcomes instead.Docs to update. The one existing page that explicitly names
asequential/astartis:docs/concepts/process.mdx— search results show it mentionsasequentialin a CardGroup withastart(). DO NOT edit this file (concepts folder is human-approved only). Instead:docs/features/async-process-cascade.mdx— a short user-facing page explaining thatasequential()/astart()now match the sync path's failure-cascade behaviour. Add aNotelinking back from the concepts page in a follow-up human review.docs/features/(e.g.docs/features/async.mdxif it exists — check first) to add a## Failure cascade inasequential()`` section.Content the page must contain:
asequential()now skips downstream tasks whose upstream dependency permanently failed, matching whatsequential()already did.""skipped: upstream failed"instead of running against empty context.sequenceDiagrammermaid showing:astart → task A (fails) → check cascade → skip B → skip C.Fix 2 · Tool executor pool recycling on
tool_timeoutWhat changed (verified against
praisonaiagents/agent/tool_execution.py):Before this PR, when a tool call hung past
tool_timeout,future.cancel()could not stop a thread that had already started, so the hung worker permanently occupied one of the pool's 2 worker slots. Two consecutive hangs would deadlock the pool. Now, on timeout, the executor isshutdown(wait=False)and dropped; the next tool call spins up a fresh executor with a fresh worker.User impact. Any long-running agent that occasionally hits
tool_timeout(network stalls, misbehaving APIs, unresponsive shells) is now self-healing at the pool level rather than progressively degrading toward a deadlock. This directly affects users who set aggressivetool_timeoutvalues on flaky third-party tools.Docs to update:
docs/configuration/tool-config.mdx— the existingtool_timeoutdocumentation. Add a short<Note>(or a "Reliability after a timeout" subsection) explaining pool recycling.docs/features/yaml-configuration-reference.mdx— thetool_timeoutrow already documents behaviour extensively. Append one sentence: "When a tool call exceeds the timeout the executor pool is recycled — the next call gets a fresh worker so a hung tool cannot progressively degrade throughput. See PR #3960."docs/features/concurrency.mdx(if present — check first, this is referenced fromyaml-configuration-reference.mdx) — add a short callout to the timeout-precedence table.Content the note must contain:
tool_timeoutno longer holds its worker slot — the pool is recycled and the next call starts on a fresh worker."Fix 3 · Doom-loop
NO_PROGRESSrecency filterWhat changed (verified against
praisonaiagents/escalation/doom_loop.py):Before this PR,
_check_no_progress()counted every progress marker for the whole session (there was a literal# Could add timestamp filteringTODO). So one early successful tool call disabledNO_PROGRESSdetection for the rest of the run, no matter how long the agent then spun on unproductive work. Now markers are stored as(marker, timestamp)tuples and only markers within the current no-progress window (the boundary is the timestamp of the action immediately preceding the window) are counted.User impact.
NO_PROGRESSdoom-loop detection actually fires now on runs where one early success previously suppressed it forever. Long-running autonomous agents will exit stuck states via the recovery path instead of burning the full iteration budget silently.Important context for the docs agent: There are two separate loop-detection subsystems in this codebase, and they are easy to confuse:
praisonaiagents/agent/loop_detection.py—LoopDetectionConfig,LoopDetectordocs/features/doom-loop-detection.mdx(existing)RecoveryAction)praisonaiagents/escalation/doom_loop.py—DoomLoopConfig,DoomLoopDetector,DoomLoopType.NO_PROGRESSdocs/sdk/reference/praisonaiagents/{classes,modules,functions}/DoomLoop*PR #3960's fix is on the escalation subsystem — the one that currently has no user-facing page.
docs/features/autonomy-loop.mdxreferencesdoom_loop_threshold(viaAutonomyConfig) and thedoom_loopcompletion reason, but does not explain the escalationDoomLoopDetectoritself.Docs to update:
docs/features/escalation-doom-loop.mdx— a new page that finally covers the escalation subsystem end-to-end (the sixDoomLoopTypevalues, theRecoveryActionladderCONTINUE → RETRY_DIFFERENT → ESCALATE_MODEL → REQUEST_HELP → ABORT, theDoomLoopConfigthresholds). This gives Fix 3 a natural home. Place underdocs/features/, notdocs/concepts/.docs/features/autonomy-loop.mdx— thedoom_loop_thresholdrow anddoom_loopcompletion-reason accordion. Add a one-line note thatNO_PROGRESSnow uses a recency window (early markers no longer suppress it) and link to the new escalation page.docs/features/doom-loop-detection.mdx— add a<Note>at the top disambiguating the two subsystems and linking to the new escalation page, so users landing on the wrong page get redirected.Content the new escalation page must contain:
title: "Escalation Doom-Loop Detection",sidebarTitle: "Escalation Doom-Loop",icon: "shield-halved".Actions/markers → DoomLoopDetector → DoomLoopType → RecoveryAction.<Steps>): (1) construct with defaults, (2) record actions + progress markers, (3) checkis_doom_loop()/get_recovery_action().DoomLoopTypeenum values (REPEATED_ACTION,REPEATED_FAILURE,NO_PROGRESS,CIRCULAR_PLAN,RESOURCE_EXHAUSTION,REPEATED_OUTPUT) with what each detects.RecoveryActionenum values (CONTINUE,RETRY_DIFFERENT,ESCALATE_MODEL,REQUEST_HELP,ABORT) with when each is chosen (verified from_determine_recovery_action: first attempt →RETRY_DIFFERENT; second attempt withescalate_on_loop=True→ESCALATE_MODEL; thenREQUEST_HELP;RESOURCE_EXHAUSTIONalways →ABORT;_recovery_attempts >= max_recovery_attempts→ABORT).DoomLoopConfig(verified from source):max_identical_actions: int = 3max_similar_actions: int = 5max_consecutive_failures: int = 3max_no_progress_steps: int = 5max_time_per_action: float = 60.0max_total_time: float = 300.0enable_auto_recovery: bool = Truemax_recovery_attempts: int = 2escalate_on_loop: bool = Trueinitial_backoff: float = 1.0backoff_multiplier: float = 2.0max_backoff: float = 30.0max_repeated_chunks: int = 8content_chunk_size: int = 50similarity_threshold: float = 0.85is kept for backward compatibility only and is not consulted (the docstring in source is explicit about this).mark_progress("read config.yaml")no longer suppressesNO_PROGRESSfor the rest of the run — markers older than the current window boundary are filtered out.<AccordionGroup>(callmark_progress()on real progress, not just any log line; keepmax_no_progress_stepssmall enough to catch stalls quickly).Placement checklist (per repo AGENTS.md)
docs/features/(ordocs/configuration/for Fix 2's tool-config sibling). Nothing underdocs/concepts/.docs.jsonto add new pages under the Features group, never under Concepts.<Steps>) → How It Works → Configuration Options table → Common Patterns → Best Practices (<AccordionGroup>) → Related (<CardGroup>).#8B0000,#189AB4,#10B981,#F59E0B,#6366F1) with white text and#7C90A0stroke.from praisonaiagents import Agentetc.). No placeholder values.mervinpraison/praisonai@main. Re-verify by reading the linked source files above before writing.Out of scope
docs/features/doom-loop-detection.mdxbeyond adding the disambiguation<Note>— that page correctly documents the result-aware subsystem and its content is accurate as of PR #3005 / #3877.docs/sdk/reference/**— those pages are regenerated from source.docs/js/) or Rust (docs/rust/) trees — those are managed by the parity system.Filed by the docs-triage scheduled routine on 2026-08-16. Verified against upstream commit
4c202929717a34838d3eb3f86d2448f21020bd9d.