fix(coding-agent): resume a provider-error-blocked goal on the next user message - #688
Conversation
…ser message A terminal provider error is infrastructure, not a decision, but the goal it blocked stayed blocked: only the cap, repetition, and length guards counted as mechanical, so the user's next message - the natural retry signal - did nothing, and the blocked notice never said how to recover. Export PROVIDER_ERROR_BLOCKED_REASON, classify it as a mechanical continuation block so the existing direct-input reactivation path picks it up, have index.ts write that shared constant instead of a duplicated literal, and append the resume hint to the blocked notice. Intentional blocks - a user interrupt or a model-declared update_goal block - stay non-recoverable.
c2af293 to
c3f2997
Compare
|
Rebased onto #687 and this PR are complementary halves of the same recovery story and both landed the same day:
Without this PR, a provider outage mid-session still stranded the goal until the user quit and relaunched senpi. Rebase touched Re-verified on the new base: Note on the earlier red run: |
Problem
A goal blocked by a terminal provider error stayed blocked forever.
agent_endwritesblockedwith reasonprovider error ended the turn (retries exhausted)whenAgentEndEvent.willRetryis false, butMECHANICAL_CONTINUATION_BLOCKSonly listed thecap, repetition, and length guards.
GoalDirectInputLifecycle.onDispositionreactivates ablocked goal on accepted direct input only for mechanical blocks, so the user's next
message — the natural retry signal — did nothing, and the blocked notice never said how to
recover.
Change
continuation-recovery.tsexportsPROVIDER_ERROR_BLOCKED_REASONand adds it toMECHANICAL_CONTINUATION_BLOCKS.index.tswrites that shared constant instead of repeating the literal reason, andappends
continuationCapRecoveryHint(...)to the blocked notice so the warning ends withSend any message to resume.direct-input-lifecycle.tsneeded no change: the reactivation path already existed; theprovider-error reason now flows through it.
Intentional blocks stay non-recoverable —
user interrupted the turnand model-declaredupdate_goalblocks encode a decision to stop, and a new regression test pins that.Prior art check: codex
ext/goal(runtime.rsstop_active_goal_for_turn) also marks aturn error
Blocked, but its resume is user/system controlled with no user-messageauto-resume — so this is a deliberate senpi-side policy improvement, not a parity port.
Evidence
RED (before the production change), both criteria failing for the right reason:
GREEN:
(goal-cap-recovery-guidance, goal-extension, issue-447-goal-continuation, goal-modules, goal-store)
Real-surface run against the actual store + lifecycle modules in a temp goal dir:
Root
npm run check: clean.Docs
goal/changes.mdgets a dated fork entry with merge-conflict zones;goal/AGENTS.mdCONTINUATION POLICY now states the mechanical/intentional split.
Summary by cubic
Treats terminal provider errors as mechanical blocks so a new user message resumes the goal. The warning now includes “Send any message to resume.”
PROVIDER_ERROR_BLOCKED_REASONtoMECHANICAL_CONTINUATION_BLOCKS, enabling automatic reactivation via existing direct-input handling.index.tsto use the shared reason and appendcontinuationCapRecoveryHint(...)to the warning.update_goal) remain non-recoverable; tests and docs updated.Written for commit c3f2997. Summary will update on new commits.