`DBOSRuntime` starts every control loop directly, so
`Workflow(num_concurrent_runs=...)` only limits runs under the basic
runtime. DBOS workers cannot apply the limit.
Now `register()` declares one DBOS queue per workflow, named
`_llamaindex_workflow_queue:<workflow_name>`, with `worker_concurrency`
set from `num_concurrent_runs`. Workflows with a limit submit through
the queue, and runs beyond the limit wait as `ENQUEUED`, admitting
within about `polling_interval_sec`. Workflows without a limit keep
starting directly, so the default path has no added latency. The
constructor rejects zero, negative, boolean, and non-integer limits.
The queue is declared even for unlimited workflows, so removing a limit
still leaves a listener for rows that were `ENQUEUED` under the old one.
Declarations live in a process-level map because DBOS's registry
survives `DBOS.destroy()` and rejects redeclaring a name. Recreating the
runtime reuses the queue object and updates its limit in place, which
DBOS's poller reads live.
The limit is per worker, so deployment capacity is the limit times the
number of live workers. Applications that restrict `DBOS.listen_queues`
collect `runtime.workflow_queues` after registering workflows and before
launch.
## Summary
Fixes an off-by-one error in retry wait-strategy indexing.
## Problem
`_ComposableRetryPolicy.next()` forwarded the runtime's 1-indexed
`attempts` value (which always starts at 1 on the first failure, see
`runtime/control_loop/reduce.py: failures = this_execution.attempts +
1`) straight to the configured wait strategy.
Every attempt-indexed wait strategy (`wait_chain`, `wait_exponential`,
`wait_incrementing`, `wait_exponential_jitter`,
`wait_random_exponential`) is documented and unit-tested as 0-indexed,
where index 0 is the delay before the first retry.
The mismatch meant every wait strategy silently skipped its first
configured delay and used the second delay for the first retry, third
for the second, and so on. For `wait_chain` specifically, this meant the
first strategy in the chain was never used at all.
## Solution
Subtract 1 from `attempts` before passing it to the wait strategy, so it
receives the 0-indexed value its contract expects.
Stop conditions are unaffected and continue to receive the original
1-indexed `attempts`, which is the correct convention for
`stop_after_attempt`.
The existing unit tests for `.next()` were also updated because they
called it directly with attempts starting at 0, an assumption that did
not match how the runtime actually invokes it. A regression test was
added that reproduces the real retry-loop calling convention end-to-end
and fails with the previous behavior.
## Tests
- Full test suite: 857 passed
- Ruff formatting/lint checks passed
- Type checking passed
Fixes#733
---------
Co-authored-by: Adrian Lyjak <adrianlyjak@gmail.com>
The LlamaAgents docs still advertise Agent Builder, including its cloud
coding workflow and dashboard instructions. This removes that page and
its overview links, while keeping the cloud deployment path pointed at
Click to Deploy.
The workflows docs had a solid API tour, but several pages taught stale
or partial contracts: custom start events carried runtime objects,
human-in-the-loop examples used older event injection patterns, retry
policy docs omitted the durable jitter seed, and the top-level guide did
not give readers a clear path through events, state, resources,
streaming, validation, and testing.
This tightens the core workflow docs around the contracts users actually
need:
- reshapes the landing guide around event flow, validation, and when to
use typed returns, `list[E]`, `ctx.send_event`, state, and resources
- adds a testing guide for `WorkflowTestRunner`, streamed events, final
results, and context state assertions
- clarifies the boundary between serializable state, durable snapshots,
and injected resources
- updates streaming docs for `WorkflowHandler`, single-consumer streams,
and abnormal workflow termination events
- fixes advanced pages for custom start/stop events, human-in-the-loop
resume, retry policy authoring, workflow drawing, and free-function step
registration
- corrects stale imports and commands for workflow server docs
- moves testing before observability/deployment/client in the sidebar so
readers hit local development guidance before server/client material
Push-mode updates were too eager: `edit`, `apply`, and `update`
configured the deployment remote before pushing, so any current git repo
could become the source repo by accident.
This keeps create smooth, but makes updates require an existing
`llamaagents-<deployment>` remote before auto-pushing. `--push` is the
explicit escape hatch for linking and pushing the current repo, and
`--no-push` still skips the push entirely.
Docs now describe the safer default for push-mode deployments.
Repo has been renamed from workflows-py to llama-agents. Update remaining
references in README, docs, CI workflows, and example notebooks.
Co-authored-by: Claude <noreply@anthropic.com>
Add documentation explaining that changes made during Agent Builder sessions
don't automatically update existing deployments. Users must explicitly push
changes to GitHub and redeploy to update their production workflow. This
enables safe experimentation without affecting stable deployments.
https://claude.ai/code/session_01Rok9AU8XDhhgH1x3FhbhGg
Co-authored-by: Claude <noreply@anthropic.com>
* prep packages
* wrong coverage detection I think
* add ts deps?
* extend path for namespaces
* the big move, import issues though
* llama_index.workflows
* llama_agents
* alias may be working
* add type checker ignores for workflows secondary namespace, update ty, and add re-exports
* Add descriptions to new client and server packages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Rather, commit the outputs
* keep the py files separate
* ignore examples deps
* Create afraid-books-own.md
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add guide for handling blocking code in workflows
Workflows run on asyncio, so blocking calls in steps stall the entire
event loop. This new page documents best practices:
- Offload blocking I/O to threads with asyncio.to_thread
- Offload CPU-intensive work to a small dedicated thread/process pool
using loop.run_in_executor
https://claude.ai/code/session_018DdW3KPH2nt1FbWECt1uKF
* reframe to async
* docs: link to async Python overview from async workflows guide
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add brief documentation for ctx.wait_for_event() as an alternative
approach for human-in-the-loop patterns. Clarifies the replay behavior
and idempotency requirements, and recommends the event-based approach
due to its complexity.
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add dedicated StopEvent subclasses for workflow termination scenarios
Add WorkflowTerminationEvent base class and specific subclasses to provide
detailed information when workflows end abnormally:
- WorkflowTimedOutEvent: Published when workflow exceeds timeout, includes
timeout duration and list of active steps
- WorkflowCancelledEvent: Published when workflow is cancelled by user
- WorkflowFailedEvent: Published when a step fails permanently, includes
step name, exception type, and exception message
These events replace the empty StopEvent() instances that were previously
published, allowing consumers to understand why a workflow ended and take
appropriate action.
Closes: issue about providing exception information in StopEvent
* refactor: simplify termination events to inherit directly from StopEvent
* chore: add changeset
* feat: add traceback and fully qualified exception type to WorkflowFailedEvent
* feat: add retry info (attempts, elapsed_seconds) to WorkflowFailedEvent
Also fixes a bug where step_function.py used time.monotonic() instead of
time.time(), causing incompatible timestamps with first_attempt_at.
* fix lints/locks
* docs: add workflow termination events to streaming docs
---------
Co-authored-by: Claude <noreply@anthropic.com>