OpenIntent v0.17.0
Released: 2026-03-24
RFC-0026: Suspension Propagation & Retry
This release closes the three gaps left open by RFC-0025 (Human-in-the-Loop Intent
Suspension, v0.16.0): how suspension propagates through containers (intent graphs,
portfolios, plans, workflows), how agents can re-notify operators who don't respond,
and how downstream workflow phases observe upstream suspension.
Highlights
Container Propagation Rules
Five normative rules define how suspended_awaiting_input spreads through the
protocol's container hierarchy:
- Intent graph — A suspended child intent blocks all dependents from proceeding.
The parent aggregate status becomessuspended_awaiting_input. - Portfolio — The portfolio aggregate gains
has_suspended_members(bool) and
suspended_member_count(int). Two new events are emitted:
portfolio.member_suspendedandportfolio.member_resumed. - Plan / Task — When an intent suspends, its corresponding plan task transitions
toblockedwithblocked_reason: "intent_suspended"andsuspended_intent_id.
Plan progress gains asuspended_taskslist. The mirror is bidirectional: when the
intent resumes, the task unblocks automatically. - Workflow — Downstream phases that declare inputs from a suspended upstream phase
receiveUpstreamIntentSuspendedErrorat claim time rather than a generic failure.
Workflow progress gains asuspended_phaseslist. - Deadline — The suspension deadline governs expiry. It MUST NOT exceed the
portfoliodue_beforeconstraint if the intent is a portfolio member.
Human Re-Notification: HumanRetryPolicy
A new dataclass for configuring automatic re-notification when operators don't respond
to a suspension within the initial timeout_seconds window.
from openintent import HumanRetryPolicy, EscalationStep
policy = HumanRetryPolicy(
max_attempts=3,
interval_seconds=3600, # 1 hour between attempts
strategy="exponential", # fixed | linear | exponential
escalation_ladder=[
EscalationStep(
attempt=2,
channel_hint="slack",
notify_to="manager@example.com",
),
EscalationStep(
attempt=3,
channel_hint="sms",
notify_to="+15551234567",
),
],
final_fallback_policy="escalate", # fail | escalate | auto_resume | skip
)On each re-notification attempt the SDK:
- Re-fires
@on_input_requestedhooks - Emits
intent.suspension_renotifiedevent - Fires
intent.suspension_escalatedwhen an escalation ladder step is reached - Applies
final_fallback_policyonce all attempts are exhausted
EscalationStep
EscalationStep(attempt=2, channel_hint="slack", notify_to="manager@example.com")Backwards-compatible field aliases: after_attempt, channel, notify.
Three-Level Policy Cascade
Retry policy resolution follows a cascade so platform operators can set a sensible
default without requiring every agent to configure one explicitly:
request_input(retry_policy=...) ← call-site (highest priority)
→ BaseAgent.default_human_retry_policy ← agent class default
→ server suspension.default_retry_policy ← platform default (lowest)
UpstreamIntentSuspendedError
from openintent import UpstreamIntentSuspendedError
try:
spec.validate_claim_inputs(task_id, completed_outputs)
except UpstreamIntentSuspendedError as e:
print(e.task_id) # the claiming task
print(e.phase_name) # the upstream phase that is suspended
print(e.suspended_intent_id)
print(e.expected_resume_at) # ISO 8601, may be NoneRaised by WorkflowSpec.validate_claim_inputs() when a declared input references an
upstream phase whose intent is currently suspended_awaiting_input. Subclass of
WorkflowError.
New EventType Constants
| Constant | Fired when |
|---|---|
EventType.SUSPENSION_RENOTIFIED |
Each re-notification attempt fires |
EventType.SUSPENSION_ESCALATED |
An escalation ladder step fires |
EventType.PORTFOLIO_MEMBER_SUSPENDED |
A portfolio member intent suspends |
EventType.PORTFOLIO_MEMBER_RESUMED |
A portfolio member intent resumes |
retry_policy on SuspensionRecord
SuspensionRecord gains an optional retry_policy: HumanRetryPolicy | None field.
Existing single-attempt behaviour is fully preserved when the field is absent.
timeout_seconds is now documented as a per-attempt window when retry_policy is set.
New Exports
from openintent import (
HumanRetryPolicy,
EscalationStep,
UpstreamIntentSuspendedError,
)Cross-RFC Patches
| RFC | Change |
|---|---|
| RFC-0002 | suspended_awaiting_input counter in aggregate status; completion-gate clarification |
| RFC-0007 | Portfolio suspension-aware aggregate fields; member_suspended / member_resumed events |
| RFC-0010 | Relationship note — RFC-0010 retries on agent failure; RFC-0026 re-notifies on human non-response |
| RFC-0012 | Task/intent suspension mirror; suspended_tasks in plan progress |
| RFC-0024 | UpstreamIntentSuspendedError at claim time; suspended_phases in workflow progress |
| RFC-0025 | retry_policy on SuspensionRecord; per-attempt timeout_seconds semantics; fallback_policy alias; extended cross-RFC table |
What's Not Changed
- Existing single-attempt
request_input()calls require no changes. - Agents that don't set
retry_policybehave exactly as before. suspended_awaiting_inputstatus is still intent-local — it is not the same as
completed, and dependents remain blocked (not failed) until the intent resumes.
Testing
- 41 new tests in
test_hitl.pyandtest_workflow_io.py - 1052+ tests across 19 test files — all passing
ruff checkclean (80 files, 0 issues)mkdocs build --strictpasses
Installation
pip install openintent==0.17.0Or with server extras:
pip install "openintent[server]==0.17.0"Full Changelog
See CHANGELOG.md for the complete change log.