What invariants must survive when a delegated sub-agent is recalled mid-execution?
**Date of Record:** 2026 October 27 **Scope:** Applies when an orchestrating agent recalls (withdraws) a delegated sub-agent's task while the sub-agent is still executing, and the sub-agent must distinguish between graceful recall (stop new work, complete in-flight actions) and forced termination (kill mid-flight). Does not apply to sessions where delegation is strictly synchronous or the sub-agent completes before recall is possible.
Core Distinction
The separation between **recall** (the orchestrator withdraws intent but allows in-flight actions to complete) and **termination** (the orchestrator kills the sub-agent immediately, discarding all pending work). Relying on termination semantics for recall creates the **Ghost Action Hazard**: the sub-agent is "recalled" but continues to complete its in-flight side effects (payments, writes, dispatches) because the recall instruction only arrived at the sub-agent's loop boundary. The orchestrator believes the sub-agent stopped, but the external world sees completed actions the orchestrator never authorized.
What Was Observed
In hierarchical agent architectures, failing to distinguish recall from termination produces three primary failure modes:
- **Post-Recall Side Effects:** The orchestrator recalls a sub-agent that is mid-payment. The sub-agent finishes the payment, but the orchestrator has already started a compensating transaction. The user is refunded twice.
- **Lost Completion Receipts:** The sub-agent is terminated mid-flight. Its in-flight work is discarded, but the external side effect already occurred. The orchestrator has no receipt and cannot reconcile.
- **Recall Race Conditions:** Two orchestrator threads race to recall and re-delegate the same sub-agent. The sub-agent receives both instructions and executes both, causing duplicate actions.
Recorded Elements
- **`recall_intent_receipt`:** A cryptographically signed recall instruction from the orchestrator, timestamped and sequence-numbered, distinct from a termination instruction.
- **`in_flight_action_manifest`:** The sub-agent's declaration of which actions were already dispatched when it received the recall, allowing the orchestrator to know what is completing.
- **`completion_grace_window`:** The maximum time the sub-agent is allowed to complete in-flight actions before forced termination, bounding the orchestrator's wait.
- **`post_recall_reconciliation_predicate`:** The exact probe the orchestrator must run after the grace window to verify which in-flight actions actually completed.
Condition of Validity
* **Applies when:** Delegation is asynchronous, the sub-agent can produce external side effects, and the orchestrator may need to recall the sub-agent before completion. * **Does not apply when:** Delegation is strictly synchronous, or the sub-agent's actions are strictly idempotent and safe to duplicate.
Decision Boundary
- **Is the recall intent or a termination intent being sent?** If termination, force-kill the sub-agent and accept side-effect loss. If recall, proceed.
- **Has the sub-agent acknowledged the recall and declared its in-flight actions?** If no, the orchestrator cannot know what is completing. It must wait for the manifest or escalate.
- **Has the completion grace window elapsed?** If no, the sub-agent may still be finishing. Wait and re-poll. If yes, proceed to reconciliation.
- **Conversely, when can an orchestrator skip reconciliation after recall?** Only when the sub-agent has provably completed zero external side effects (strictly read-only or fully idempotent actions) and the sub-agent has signed a "no side effects" receipt.
Operational Effect
- **Ghost Action Prevention:** Prevents duplicate or unauthorized side effects from recalled sub-agents by tracking in-flight actions.
- **Reconciliation Determinism:** Provides a bounded reconciliation protocol, allowing the orchestrator to know exactly which actions completed.
- **Recall Race Prevention:** Sequence-numbered recall intents prevent duplicate recall-then-redelegate races.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class RecallVerdict(Enum):
RECALL_ACTIVE = auto()
RECALL_INFLIGHT_ACTIONS_PENDING = auto()
RECALL_COMPLETION_GRACE_EXPIRED = auto()
RECALL_NO_SIDE_EFFECTS_SAFE = auto()
@dataclass(frozen=True)
class RecallState:
subagent_id: str
recall_intent_sequence: int
recall_intent_epoch: float
in_flight_actions: tuple[str, ...]
completion_grace_remaining_sec: float
no_side_effects_receipt: str | None
current_epoch: float
@dataclass(frozen=True)
class RecallPolicy:
max_grace_sec: float
@dataclass(frozen=True)
class RecallManifest:
verdict: RecallVerdict
directive: str
manifest_digest: str
class SubAgentRecallGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_recall(
cls, state: RecallState, policy: RecallPolicy
) -> RecallManifest:
# Gate 1: No side effects receipt
if state.no_side_effects_receipt and not state.in_flight_actions:
digest = cls._hash(json.dumps({
"subagent": state.subagent_id,
"sequence": str(state.recall_intent_sequence),
"receipt": state.no_side_effects_receipt
}, sort_keys=True))
return RecallManifest(
verdict=RecallVerdict.RECALL_NO_SIDE_EFFECTS_SAFE,
directive="PROCEED: No side effects, recall safe to complete.",
manifest_digest=digest
)
# Gate 2: In-flight actions
if state.in_flight_actions:
if state.completion_grace_remaining_sec > 0:
return RecallManifest(
verdict=RecallVerdict.RECALL_INFLIGHT_ACTIONS_PENDING,
directive=f"WAIT: {len(state.in_flight_actions)} actions in flight. {state.completion_grace_remaining_sec:.0f}s grace remaining.",
manifest_digest=cls._hash(f"IN_FLIGHT:{len(state.in_flight_actions)}")
)
return RecallManifest(
verdict=RecallVerdict.RECALL_COMPLETION_GRACE_EXPIRED,
directive=f"HALT: Grace expired with {len(state.in_flight_actions)} actions possibly still completing. Reconcile.",
manifest_digest=cls._hash(f"GRACE_EXPIRED:{len(state.in_flight_actions)}")
)
# Gate 3: Grace exceeded max
if state.completion_grace_remaining_sec > policy.max_grace_sec:
return RecallManifest(
verdict=RecallVerdict.RECALL_INFLIGHT_ACTIONS_PENDING,
directive=f"HALT: Declared grace {state.completion_grace_remaining_sec:.0f}s > max {policy.max_grace_sec:.0f}s.",
manifest_digest=cls._hash(f"GRACE_TOO_LONG:{state.completion_grace_remaining_sec:.0f}")
)
# Gate 4: Active
digest = cls._hash(json.dumps({
"subagent": state.subagent_id,
"sequence": str(state.recall_intent_sequence),
"epoch": f"{state.recall_intent_epoch:.0f}"
}, sort_keys=True))
return RecallManifest(
verdict=RecallVerdict.RECALL_ACTIVE,
directive="PROCEED: Recall active, no side effects detected.",
manifest_digest=digest
)