Skip to content

v0.17.0

Latest

Choose a tag to compare

@nirvanatikku nirvanatikku released this 24 Mar 23:44
f9ebb35

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:

  1. Intent graph — A suspended child intent blocks all dependents from proceeding.
    The parent aggregate status becomes suspended_awaiting_input.
  2. Portfolio — The portfolio aggregate gains has_suspended_members (bool) and
    suspended_member_count (int). Two new events are emitted:
    portfolio.member_suspended and portfolio.member_resumed.
  3. Plan / Task — When an intent suspends, its corresponding plan task transitions
    to blocked with blocked_reason: "intent_suspended" and suspended_intent_id.
    Plan progress gains a suspended_tasks list. The mirror is bidirectional: when the
    intent resumes, the task unblocks automatically.
  4. Workflow — Downstream phases that declare inputs from a suspended upstream phase
    receive UpstreamIntentSuspendedError at claim time rather than a generic failure.
    Workflow progress gains a suspended_phases list.
  5. Deadline — The suspension deadline governs expiry. It MUST NOT exceed the
    portfolio due_before constraint 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_requested hooks
  • Emits intent.suspension_renotified event
  • Fires intent.suspension_escalated when an escalation ladder step is reached
  • Applies final_fallback_policy once 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 None

Raised 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_policy behave exactly as before.
  • suspended_awaiting_input status 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.py and test_workflow_io.py
  • 1052+ tests across 19 test files — all passing
  • ruff check clean (80 files, 0 issues)
  • mkdocs build --strict passes

Installation

pip install openintent==0.17.0

Or with server extras:

pip install "openintent[server]==0.17.0"

Full Changelog

See CHANGELOG.md for the complete change log.