@crewhaus/recovery-engine 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,14 @@
12
12
  * user-aborted → fail("user_aborted") — orchestrator handles abort separately
13
13
  * anything else → fail(message)
14
14
  *
15
+ * Item 23 — `switch-model` is an OPT-IN `failure_taxonomy` recovery action
16
+ * (never a built-in `classify()` verdict, so default runs are byte-for-byte
17
+ * unchanged). It tells the orchestrator to route the same turn onto the
18
+ * next provider-failover candidate (open the active breaker so the chain
19
+ * reroutes) instead of exhausting backoff retries against a dead provider.
20
+ * Budgeted per turn like the others; a run without a failover chain treats
21
+ * it as a retry-shaped no-op re-issue (documented in runtime-core).
22
+ *
15
23
  * References: claude-code/query.ts recovery branches; agent-framework
16
24
  * _runner.py retry; AI-Harness-Systems §recovery.
17
25
  */
@@ -27,6 +35,8 @@ export type RecoveryAction = {
27
35
  } | {
28
36
  readonly kind: "tombstone";
29
37
  readonly messageId?: string;
38
+ } | {
39
+ readonly kind: "switch-model";
30
40
  } | {
31
41
  readonly kind: "fail";
32
42
  readonly reason: string;
@@ -41,6 +51,8 @@ export type RecoveryState = {
41
51
  readonly continueCount: number;
42
52
  /** Number of `tombstone` actions already chosen in this turn. */
43
53
  readonly tombstoneCount: number;
54
+ /** Item 23 — number of `switch-model` actions already chosen this turn. */
55
+ readonly switchModelCount: number;
44
56
  };
45
57
  export declare const initialRecoveryState: RecoveryState;
46
58
  export declare class RecoveryEngineError extends CrewhausError {
@@ -71,7 +83,7 @@ export declare function classify(error: unknown): RecoveryErrorClass;
71
83
  export type NamedFailureClass = {
72
84
  readonly class: string;
73
85
  readonly pattern: string;
74
- readonly recovery: "retry" | "compact" | "continue" | "tombstone" | "fail";
86
+ readonly recovery: "retry" | "compact" | "continue" | "tombstone" | "switch-model" | "fail";
75
87
  readonly hint?: string;
76
88
  };
77
89
  /**
@@ -101,6 +113,7 @@ export declare const BUDGETS: {
101
113
  readonly MAX_COMPACTS: 1;
102
114
  readonly MAX_CONTINUES: 3;
103
115
  readonly MAX_TOMBSTONES: 1;
116
+ readonly MAX_SWITCH_MODELS: 3;
104
117
  readonly BASE_BACKOFF_MS: 1000;
105
118
  readonly MAX_BACKOFF_MS: 30000;
106
119
  readonly MAX_JITTER_MS: 250;
package/dist/index.js CHANGED
@@ -12,6 +12,14 @@
12
12
  * user-aborted → fail("user_aborted") — orchestrator handles abort separately
13
13
  * anything else → fail(message)
14
14
  *
15
+ * Item 23 — `switch-model` is an OPT-IN `failure_taxonomy` recovery action
16
+ * (never a built-in `classify()` verdict, so default runs are byte-for-byte
17
+ * unchanged). It tells the orchestrator to route the same turn onto the
18
+ * next provider-failover candidate (open the active breaker so the chain
19
+ * reroutes) instead of exhausting backoff retries against a dead provider.
20
+ * Budgeted per turn like the others; a run without a failover chain treats
21
+ * it as a retry-shaped no-op re-issue (documented in runtime-core).
22
+ *
15
23
  * References: claude-code/query.ts recovery branches; agent-framework
16
24
  * _runner.py retry; AI-Harness-Systems §recovery.
17
25
  */
@@ -21,6 +29,7 @@ export const initialRecoveryState = {
21
29
  compactCount: 0,
22
30
  continueCount: 0,
23
31
  tombstoneCount: 0,
32
+ switchModelCount: 0,
24
33
  };
25
34
  export class RecoveryEngineError extends CrewhausError {
26
35
  name = "RecoveryEngineError";
@@ -32,6 +41,10 @@ const MAX_RETRIES = 5;
32
41
  const MAX_COMPACTS = 1;
33
42
  const MAX_CONTINUES = 3;
34
43
  const MAX_TOMBSTONES = 1;
44
+ // Item 23 — cap `switch-model` hops per turn. Generous enough to walk a
45
+ // short failover chain (primary → fallback → fallback), low enough that a
46
+ // mutually-degraded chain fails the turn instead of looping forever.
47
+ const MAX_SWITCH_MODELS = 3;
35
48
  const BASE_BACKOFF_MS = 1_000;
36
49
  const MAX_BACKOFF_MS = 30_000;
37
50
  const MAX_JITTER_MS = 250;
@@ -202,6 +215,11 @@ function recoverNamed(named, state) {
202
215
  return { kind: "fail", reason: `tombstone budget exhausted: ${reason}` };
203
216
  }
204
217
  return { kind: "tombstone" };
218
+ case "switch-model":
219
+ if (state.switchModelCount >= MAX_SWITCH_MODELS) {
220
+ return { kind: "fail", reason: `switch-model budget exhausted: ${reason}` };
221
+ }
222
+ return { kind: "switch-model" };
205
223
  case "fail":
206
224
  return { kind: "fail", reason };
207
225
  }
@@ -220,6 +238,8 @@ export function advanceState(state, action) {
220
238
  return { ...state, continueCount: state.continueCount + 1 };
221
239
  case "tombstone":
222
240
  return { ...state, tombstoneCount: state.tombstoneCount + 1 };
241
+ case "switch-model":
242
+ return { ...state, switchModelCount: state.switchModelCount + 1 };
223
243
  case "fail":
224
244
  return state;
225
245
  }
@@ -230,6 +250,7 @@ export const BUDGETS = {
230
250
  MAX_COMPACTS,
231
251
  MAX_CONTINUES,
232
252
  MAX_TOMBSTONES,
253
+ MAX_SWITCH_MODELS,
233
254
  BASE_BACKOFF_MS,
234
255
  MAX_BACKOFF_MS,
235
256
  MAX_JITTER_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/recovery-engine",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Pure recovery taxonomy: classify Anthropic API errors into a RecoveryAction (compact/retry/continue/tombstone/fail)",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/errors": "0.1.8"
18
+ "@crewhaus/errors": "0.2.0"
19
19
  },
20
20
  "license": "Apache-2.0",
21
21
  "author": {