A denied tool call must not become an automatic retry
LangChain commit 17d1c274 changes the default human-in-the-loop rejection message. The model is told that the tool did not execute and should not retry the same call unless the user asks. An integration test resumes after rejection and asserts that the agent does not interrupt for the tool again.
The host workflow owns the interrupt, rejection decision, and resume transition. The model loop chooses what to propose after receiving the rejection. Policy prevents execution; the message guides later behavior. Those are different controls.
What changed since the first Atlas draft
An on-exit wake turns loop continuation into scheduled runtime behavior.
charmbracelet/crush Crush Stops Writing To Vanished Shell SessionsShell persistence needs failure-aware loop boundaries.
anomalyco/opencode OpenCode's Hardening Day Removes Layer ShortcutsHardening work shows loops relying on layer discipline, not just prompt quality.
What actually happens
-
Load run state
identity, queue, budgets
-
Propose action
probabilistic next step
-
Gate transition
authorize, block, pause
-
Bound executor
invoke selected principal
-
Environment effect
may commit before reply
-
Observed outcome
known success or failure
-
Unknown outcome
effect may exist
-
Reconcile before retry
lookup, dedupe, compensate
A loop is not a workflow
In an agent loop, observations return to a model that may propose another tool call, produce an answer, or stop. The next action is partly probabilistic. A workflow instead defines host-owned transitions through a sequence, state machine, queue, event handler, or graph.
A deterministic workflow may contain probabilistic model calls without surrendering control of its edges. That distinction determines where budgets, stop conditions, approvals, and recovery rules can be enforced.
Repeated execution requires explicit state ownership
Codex replaces an override side channel with queued ThreadSettings and moves pending input, next-turn items, and mailbox coordination into InputQueue. Ordered state prevents later iterations from observing an accidental mixture of settings and input.
Concurrent work also needs narrower identity than a session ID. Crush carries a caller-supplied RunID through error events and ignores failures from unrelated prompts sharing the event channel. Without that boundary, one run can capture another run's failure.
Reject, cancel, rewind, and compensate are different
A rejection means an action was not authorized. Cancellation means active work must receive a termination path. Codex keeps interrupted shell dispatch alive long enough to send SIGTERM, wait briefly, and then hard-kill remaining descendants.
Hermes undo marks transcript rows inactive, retains them for audit, evicts cached state, and rebuilds from active history. It rewinds conversational state, not files, API calls, or processes already changed. External effects need separate compensation.
A timeout creates another state: the effect may have committed even though the runtime never observed success. Safe recovery needs an explicit unknown-outcome state, idempotency keys or deduplication, and an effect journal before any retry.
Reliability belongs in transition tests
Tests should assert transition invariants, not merely inspect a final answer: a rejected tool is not re-invoked, a foreign RunID error does not abort the current run, cancellation emits a terminal state, and rewind excludes inactive rows from the next context.
A serious test rig records workflow, run, attempt, source revision, provider, and environment metadata. These controls separate model variance from orchestration races and test-environment failures.
Where the contract breaks
Unbounded retry
A denial or deterministic failure is interpreted as a transient observation.
Cross-run event capture
Session-wide events are treated as belonging to every concurrent run.
Transcript rollback mistaken for world rollback
Removing messages is assumed to reverse performed actions.
Unknown outcome after timeout
The environment commits an effect but the runtime loses the success response.
What the product must prove
- Label every transition as model-owned, workflow-owned, policy-owned, human-owned, or executor-owned.
- Enumerate budgets, stop predicates, terminal outcomes, cancellation paths, checkpoints, and irreversible effects.
- Replay denial, cancellation, foreign-event, and rewind traces; assert state and side-effect invariants.
- Force a timeout after an external commit; block retry unless the runtime can prove whether the effect occurred.