runward 0.6.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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +128 -0
  4. package/dist/cli.js +76 -0
  5. package/dist/commands/check.js +50 -0
  6. package/dist/commands/doctor.js +75 -0
  7. package/dist/commands/init.js +90 -0
  8. package/dist/commands/status.js +49 -0
  9. package/dist/commands/update.js +48 -0
  10. package/dist/lib/constants.js +2 -0
  11. package/dist/lib/mission.js +96 -0
  12. package/dist/lib/paths.js +28 -0
  13. package/dist/lib/styles.js +60 -0
  14. package/dist/lib/tools.js +59 -0
  15. package/dist/lib/write.js +30 -0
  16. package/package.json +29 -0
  17. package/templates/mission/adr/ADR-0000-template.md +38 -0
  18. package/templates/mission/architecture.md +60 -0
  19. package/templates/mission/decision-matrix.md +37 -0
  20. package/templates/mission/evaluation-rubric.md +60 -0
  21. package/templates/mission/floor.md +47 -0
  22. package/templates/mission/framing.md +58 -0
  23. package/templates/mission/mission-contract.md +58 -0
  24. package/templates/mission/observability-schema.md +53 -0
  25. package/templates/mission/port-contract.md +63 -0
  26. package/templates/mission/reference-stack.md +70 -0
  27. package/templates/mission/runbook.md +70 -0
  28. package/templates/mission/shared-bricks.md +74 -0
  29. package/templates/mission/threat-model.md +53 -0
  30. package/templates/rules/async-job-guardrails.md +95 -0
  31. package/templates/rules/async-post-turn-pipeline.md +96 -0
  32. package/templates/rules/async-scheduled-maintenance.md +95 -0
  33. package/templates/rules/cache-three-tier-architecture.md +44 -0
  34. package/templates/rules/checklist-day-zero-project.md +97 -0
  35. package/templates/rules/checklist-pre-production-observability.md +99 -0
  36. package/templates/rules/checklist-pre-production-performance.md +109 -0
  37. package/templates/rules/checklist-pre-production-resilience.md +99 -0
  38. package/templates/rules/checklist-pre-production-security.md +86 -0
  39. package/templates/rules/config-secrets-boundary.md +57 -0
  40. package/templates/rules/config-typing-zod.md +78 -0
  41. package/templates/rules/contracts-governance.md +64 -0
  42. package/templates/rules/data-memory-consolidation.md +81 -0
  43. package/templates/rules/data-memory-invalidation.md +90 -0
  44. package/templates/rules/data-memory-scoring.md +154 -0
  45. package/templates/rules/data-migrations-forward-only.md +55 -0
  46. package/templates/rules/data-orphan-cleanup.md +142 -0
  47. package/templates/rules/data-ttl-types.md +100 -0
  48. package/templates/rules/eval-loop.md +52 -0
  49. package/templates/rules/frontier-deterministic-boundary.md +91 -0
  50. package/templates/rules/hexa-adapter-pattern.md +85 -0
  51. package/templates/rules/hexa-architecture.md +84 -0
  52. package/templates/rules/hexa-move-deterministic-out.md +93 -0
  53. package/templates/rules/hexa-recommended-stack.md +33 -0
  54. package/templates/rules/hexa-typescript-native.md +80 -0
  55. package/templates/rules/observability-alert-configuration.md +160 -0
  56. package/templates/rules/observability-llm-metrics.md +150 -0
  57. package/templates/rules/observability-startup-provider-log.md +128 -0
  58. package/templates/rules/observability-structured-json-logs.md +138 -0
  59. package/templates/rules/patterns-memory-router-tiered.md +139 -0
  60. package/templates/rules/patterns-prompt-compiler.md +126 -0
  61. package/templates/rules/patterns-request-id-propagation.md +137 -0
  62. package/templates/rules/process-adr-and-journal.md +50 -0
  63. package/templates/rules/provider-llm-auto-detection.md +54 -0
  64. package/templates/rules/provider-no-crash-missing-env.md +67 -0
  65. package/templates/rules/quality-codebase-metrics.md +115 -0
  66. package/templates/rules/quality-zod-input-validation.md +131 -0
  67. package/templates/rules/resilience-fail-open.md +65 -0
  68. package/templates/rules/resilience-multi-provider-fallback.md +100 -0
  69. package/templates/rules/resilience-retry-backoff.md +115 -0
  70. package/templates/rules/resilience-retryable-errors.md +107 -0
  71. package/templates/rules/routing-confidence-upgrade.md +89 -0
  72. package/templates/rules/routing-model-cost-ratios.md +68 -0
  73. package/templates/rules/routing-smart-complexity.md +131 -0
  74. package/templates/rules/scaling-db-connection-pooling.md +121 -0
  75. package/templates/rules/scaling-distributed-rate-limiting.md +141 -0
  76. package/templates/rules/scaling-state-externalization.md +84 -0
  77. package/templates/rules/security-prompt-injection.md +63 -0
  78. package/templates/rules/state-event-sourcing.md +66 -0
  79. package/templates/rules/tools-registry-pattern.md +153 -0
  80. package/templates/rules/tools-scope-atomicity.md +103 -0
  81. package/templates/targets/AGENTS.md +32 -0
  82. package/templates/workflows/architect.md +50 -0
  83. package/templates/workflows/brownfield.md +52 -0
  84. package/templates/workflows/decision-loop.md +52 -0
  85. package/templates/workflows/floor.md +60 -0
  86. package/templates/workflows/frame.md +69 -0
  87. package/templates/workflows/govern.md +55 -0
  88. package/templates/workflows/handover.md +55 -0
  89. package/templates/workflows/iterate.md +52 -0
  90. package/templates/workflows/method.md +53 -0
  91. package/templates/workflows/review.md +59 -0
@@ -0,0 +1,131 @@
1
+ ---
2
+ title: Zod Input Validation
3
+ impact: MEDIUM
4
+ impactDescription: Prevents invalid data from entering the system and provides type safety
5
+ tags: [quality, validation, security, typescript]
6
+ ---
7
+
8
+ ## Zod Input Validation
9
+
10
+ Validate all external inputs with Zod at system boundaries.
11
+
12
+ **Where to Validate:**
13
+
14
+ - API endpoints (request body, query params)
15
+ - Environment variables
16
+ - User uploads
17
+ - External API responses
18
+ - Database query results (optional, for defense in depth)
19
+
20
+ **Incorrect:**
21
+
22
+ ```typescript
23
+ // No validation - trusts external input
24
+ app.post('/api/projects', async (req, res) => {
25
+ const { name, description, budget } = req.body; // BAD: Could be anything
26
+ await db.projects.create({ data: { name, description, budget } });
27
+ });
28
+
29
+ // Type assertion without validation
30
+ const config = process.env as Config; // BAD: Not validated
31
+ ```
32
+
33
+ **Correct:**
34
+
35
+ ```typescript
36
+ import { z } from 'zod';
37
+
38
+ // Define schemas
39
+ const CreateProjectSchema = z.object({
40
+ name: z.string().min(1).max(100),
41
+ description: z.string().max(1000).optional(),
42
+ budget: z.number().positive().optional(),
43
+ tags: z.array(z.string()).max(10).optional(),
44
+ });
45
+
46
+ type CreateProjectInput = z.infer<typeof CreateProjectSchema>;
47
+
48
+ // Validate at API boundary
49
+ app.post('/api/projects', async (req, res) => {
50
+ // Parse throws ZodError if invalid
51
+ const input = CreateProjectSchema.parse(req.body);
52
+
53
+ // input is now typed and validated
54
+ await db.projects.create({ data: input });
55
+ });
56
+
57
+ // Or with safe parsing
58
+ app.post('/api/projects', async (req, res) => {
59
+ const result = CreateProjectSchema.safeParse(req.body);
60
+
61
+ if (!result.success) {
62
+ return res.status(400).json({
63
+ error: 'Validation failed',
64
+ details: result.error.flatten(),
65
+ });
66
+ }
67
+
68
+ await db.projects.create({ data: result.data });
69
+ });
70
+ ```
71
+
72
+ **Environment Variables:**
73
+
74
+ ```typescript
75
+ // src/lib/env.ts
76
+ const EnvSchema = z.object({
77
+ DATABASE_URL: z.string().url(),
78
+ MODEL_API_KEY: z.string().optional(),
79
+ EMBEDDING_API_KEY: z.string().optional(),
80
+ PORT: z.coerce.number().default(3000),
81
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
82
+ });
83
+
84
+ // Validate once at startup
85
+ export const env = EnvSchema.parse(process.env);
86
+
87
+ // Usage: env.DATABASE_URL is typed and validated
88
+ ```
89
+
90
+ **API Response Validation:**
91
+
92
+ ```typescript
93
+ // Validate external API responses
94
+ const ExternalAPIResponseSchema = z.object({
95
+ data: z.array(z.object({
96
+ id: z.string(),
97
+ name: z.string(),
98
+ value: z.number(),
99
+ })),
100
+ meta: z.object({
101
+ page: z.number(),
102
+ total: z.number(),
103
+ }),
104
+ });
105
+
106
+ async function fetchExternalData(): Promise<ExternalAPIResponse> {
107
+ const response = await fetch('https://api.external.com/data');
108
+ const json = await response.json();
109
+
110
+ // Validate response matches expected shape
111
+ return ExternalAPIResponseSchema.parse(json);
112
+ }
113
+ ```
114
+
115
+ **Error Handling:**
116
+
117
+ ```typescript
118
+ // Middleware for Zod error handling
119
+ function zodErrorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
120
+ if (err instanceof z.ZodError) {
121
+ return res.status(400).json({
122
+ error: 'Validation failed',
123
+ details: err.errors.map(e => ({
124
+ path: e.path.join('.'),
125
+ message: e.message,
126
+ })),
127
+ });
128
+ }
129
+ next(err);
130
+ }
131
+ ```
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: Fail-Open for the Non-Critical, Fail-Closed for the Sensitive
3
+ impact: CRITICAL
4
+ impactDescription: Degrades gracefully where safe and denies safely where it matters, instead of applying one failure policy everywhere
5
+ tags: [resilience, error-handling, availability, safety]
6
+ ---
7
+
8
+ ## Fail-Open for the Non-Critical, Fail-Closed for the Sensitive
9
+
10
+ > **A non-critical service down is not the application down. A sensitive action in doubt is a denied action, never a guessed one.**
11
+
12
+ The failure policy is not global. You choose it per dependency, by criticality. Getting this split wrong in either direction is a bug: fail-closed on a cache makes you brittle; fail-open on an authorisation makes you unsafe.
13
+
14
+ **Fail-open (degrade and continue) for reads and the non-critical:**
15
+
16
+ ```typescript
17
+ // Cache failure means slower, not broken.
18
+ async function getCached<T>(key: string): Promise<T | null> {
19
+ try {
20
+ return await cache.get<T>(key);
21
+ } catch (error) {
22
+ log.error("[CACHE] get failed, continuing without cache", { error });
23
+ return null; // fail-open: continue degraded
24
+ }
25
+ }
26
+ ```
27
+
28
+ **Fail-closed (deny, explicitly) for the sensitive:**
29
+
30
+ ```typescript
31
+ // Authorisation, the grounding guard, a payment: doubt means deny.
32
+ async function authorise(req: Request): Promise<void> {
33
+ try {
34
+ const ok = await authz.check(req);
35
+ if (!ok) throw new Forbidden();
36
+ } catch (error) {
37
+ // Never "allow on error". A guard you fail open is not a guard.
38
+ throw new Forbidden(); // fail-closed: deny on doubt
39
+ }
40
+ }
41
+ ```
42
+
43
+ **Suspend, do not block.** Fail-closed does not mean freezing the process. A sensitive action that needs a human serialises the agent's state and rehydrates it on the decision; low-urgency approvals are batched in a prioritised queue. You get the safety of fail-closed without a frozen agent.
44
+
45
+ **Service classification:**
46
+
47
+ | Service / action | Policy | On failure |
48
+ |---|---|---|
49
+ | Database (read of record) | Critical | Error to the user |
50
+ | Model call | Critical | Error after retries |
51
+ | Cache | Non-critical | Continue, slower (fail-open) |
52
+ | Audit / analytics logging | Non-critical | Log locally, continue (fail-open) |
53
+ | Optional enrichment (search, memory) | Non-critical | Skip, continue (fail-open) |
54
+ | Authorisation / tenant isolation | Sensitive | Deny (fail-closed) |
55
+ | Grounding / safety guard | Sensitive | Reject the response (fail-closed) |
56
+ | Payment, irreversible write | Sensitive | Abort + require confirmation (fail-closed) |
57
+
58
+ **The test that tells them apart:** ask "if this dependency is wrong or absent, is the safe answer *continue* or *deny*?" Continue -> fail-open. Deny -> fail-closed. Anything touching safety, authorisation, money, or the truth of a served figure is fail-closed.
59
+
60
+ **Checklist:**
61
+
62
+ - [ ] Every external dependency has an explicit failure policy, chosen by criticality.
63
+ - [ ] No safety/authorisation/guard path fails open.
64
+ - [ ] No read-side enrichment fails closed and takes the request down with it.
65
+ - [ ] Fail-closed sensitive actions suspend-and-rehydrate rather than freeze.
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Multi-Provider Fallback
3
+ impact: HIGH
4
+ impactDescription: Ensures LLM availability by falling back to alternative providers
5
+ tags: [resilience, llm, providers, availability]
6
+ ---
7
+
8
+ ## Multi-Provider Fallback
9
+
10
+ Configure fallback providers to handle outages. If primary provider fails, automatically try secondary.
11
+
12
+ **Model gateway configuration (one option):**
13
+
14
+ ```yaml
15
+ # gateway_config.yaml
16
+ model_list:
17
+ - model_name: app-fast
18
+ gateway_params:
19
+ model: small-fast-model
20
+ fallbacks:
21
+ - alt-small-model
22
+
23
+ - model_name: app-balanced
24
+ gateway_params:
25
+ model: mid-tier-model
26
+ fallbacks:
27
+ - alt-mid-model
28
+
29
+ - model_name: app-deep
30
+ gateway_params:
31
+ model: top-tier-model
32
+ fallbacks:
33
+ - alt-mid-model # No Deep equivalent
34
+
35
+ router_settings:
36
+ retry_policy:
37
+ num_retries: 3
38
+ backoff_factor: 2
39
+ ```
40
+
41
+ **Manual Fallback (without a model gateway):**
42
+
43
+ ```typescript
44
+ // src/lib/providers/llm-client.ts
45
+ interface LLMClient {
46
+ complete(request: CompletionRequest): Promise<CompletionResponse>;
47
+ }
48
+
49
+ const providers: LLMClient[] = [
50
+ new PrimaryModelClient(),
51
+ new SecondaryModelClient(),
52
+ new TertiaryModelClient(),
53
+ ];
54
+
55
+ async function completeWithFallback(
56
+ request: CompletionRequest
57
+ ): Promise<CompletionResponse> {
58
+ let lastError: Error;
59
+
60
+ for (const provider of providers) {
61
+ try {
62
+ return await provider.complete(request);
63
+ } catch (error) {
64
+ lastError = error as Error;
65
+ console.warn(`[LLM] Provider failed, trying next:`, {
66
+ provider: provider.constructor.name,
67
+ error: lastError.message,
68
+ });
69
+ }
70
+ }
71
+
72
+ throw new Error(`All LLM providers failed. Last error: ${lastError!.message}`);
73
+ }
74
+ ```
75
+
76
+ **Provider Priority:**
77
+
78
+ | Priority | Provider | Notes |
79
+ |----------|----------|-------|
80
+ | 1 | Primary provider | best measured quality |
81
+ | 2 | Secondary provider | availability fallback |
82
+ | 3 | Tertiary provider | last resort |
83
+ | 4 | Local or offline | development, air-gapped |
84
+
85
+ **When to use each approach:**
86
+
87
+ - **a model gateway**: Production, complex routing, multiple providers
88
+ - **Manual fallback**: Simple apps, fewer dependencies
89
+ - **Single provider**: Development, prototyping
90
+
91
+ **Monitoring:**
92
+
93
+ ```typescript
94
+ // Track fallback usage
95
+ console.info('[LLM] Request completed', {
96
+ primaryProvider: 'primary',
97
+ usedProvider: response.provider, // May differ if fallback used
98
+ fallbackUsed: response.provider !== 'primary',
99
+ });
100
+ ```
@@ -0,0 +1,115 @@
1
+ ---
2
+ title: LLM Retry with Exponential Backoff
3
+ impact: HIGH
4
+ impactDescription: Handles transient LLM failures gracefully without overwhelming the API
5
+ tags: [resilience, llm, retry, error-handling]
6
+ ---
7
+
8
+ ## LLM Retry with Exponential Backoff
9
+
10
+ LLM APIs experience rate limits and transient errors. Implement retry with exponential backoff.
11
+
12
+ **Incorrect:**
13
+
14
+ ```typescript
15
+ // No retry - single failure = user error
16
+ const response = await model.complete({...});
17
+
18
+ // Or: immediate retry flood
19
+ while (retries < 3) {
20
+ try {
21
+ return await model.complete({...});
22
+ } catch {
23
+ retries++; // BAD: No delay
24
+ }
25
+ }
26
+ ```
27
+
28
+ **Correct:**
29
+
30
+ ```typescript
31
+ // src/lib/resilience/retry.ts
32
+ interface RetryConfig {
33
+ maxRetries: number;
34
+ initialDelayMs: number;
35
+ maxDelayMs: number;
36
+ retryableErrors: string[];
37
+ }
38
+
39
+ const defaultConfig: RetryConfig = {
40
+ maxRetries: 3,
41
+ initialDelayMs: 1000,
42
+ maxDelayMs: 10000,
43
+ retryableErrors: ['rate_limit_error', 'overloaded_error', 'api_error'],
44
+ };
45
+
46
+ function isRetryable(error: unknown, retryableErrors: string[]): boolean {
47
+ if (error instanceof Error) {
48
+ return retryableErrors.some(e => error.message.includes(e));
49
+ }
50
+ return false;
51
+ }
52
+
53
+ async function sleep(ms: number): Promise<void> {
54
+ return new Promise(resolve => setTimeout(resolve, ms));
55
+ }
56
+
57
+ export async function withLLMRetry<T>(
58
+ fn: () => Promise<T>,
59
+ config = defaultConfig
60
+ ): Promise<T> {
61
+ let lastError: Error;
62
+
63
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
64
+ try {
65
+ return await fn();
66
+ } catch (error) {
67
+ lastError = error as Error;
68
+
69
+ // Don't retry non-retryable errors
70
+ if (!isRetryable(error, config.retryableErrors)) {
71
+ throw error;
72
+ }
73
+
74
+ // Don't delay after last attempt
75
+ if (attempt < config.maxRetries) {
76
+ const delay = Math.min(
77
+ config.initialDelayMs * Math.pow(2, attempt),
78
+ config.maxDelayMs
79
+ );
80
+ console.warn(`[LLM] Retry ${attempt + 1}/${config.maxRetries} after ${delay}ms`, {
81
+ error: lastError.message,
82
+ });
83
+ await sleep(delay);
84
+ }
85
+ }
86
+ }
87
+
88
+ throw lastError!;
89
+ }
90
+
91
+ // Usage
92
+ const response = await withLLMRetry(() =>
93
+ model.complete({
94
+ model: 'mid-tier-model',
95
+ messages,
96
+ })
97
+ );
98
+ ```
99
+
100
+ **Retry Delays:**
101
+
102
+ | Attempt | Delay | Total Wait |
103
+ |---------|-------|------------|
104
+ | 1 | 1000ms | 1s |
105
+ | 2 | 2000ms | 3s |
106
+ | 3 | 4000ms | 7s |
107
+
108
+ **Retryable vs Non-Retryable:**
109
+
110
+ | Retryable | Non-Retryable |
111
+ |-----------|---------------|
112
+ | `rate_limit_error` | `invalid_request_error` |
113
+ | `overloaded_error` | `authentication_error` |
114
+ | `api_error` | `invalid_api_key` |
115
+ | Network timeouts | Validation errors |
@@ -0,0 +1,107 @@
1
+ ---
2
+ title: Retryable vs Non-Retryable Errors
3
+ impact: MEDIUM
4
+ impactDescription: Prevents wasted retries and ensures quick failure for unrecoverable errors
5
+ tags: [resilience, error-handling, llm]
6
+ ---
7
+
8
+ ## Retryable vs Non-Retryable Errors
9
+
10
+ Not all errors should be retried. Distinguishing them prevents wasted time and API calls.
11
+
12
+ **Error Classification:**
13
+
14
+ | Retryable (transient) | Non-Retryable (permanent) |
15
+ |----------------------|---------------------------|
16
+ | `rate_limit_error` | `invalid_request_error` |
17
+ | `overloaded_error` | `authentication_error` |
18
+ | `api_error` (500s) | `invalid_api_key` |
19
+ | Network timeout | `model_not_found` |
20
+ | Connection reset | Validation errors |
21
+ | `service_unavailable` | `content_policy_violation` |
22
+
23
+ **Incorrect:**
24
+
25
+ ```typescript
26
+ // Retries everything, including permanent errors
27
+ async function callLLM(request: Request): Promise<Response> {
28
+ for (let i = 0; i < 3; i++) {
29
+ try {
30
+ return await model.complete(request);
31
+ } catch (error) {
32
+ await sleep(1000 * Math.pow(2, i)); // BAD: Retries invalid_api_key
33
+ }
34
+ }
35
+ throw new Error('Failed after retries');
36
+ }
37
+ ```
38
+
39
+ **Correct:**
40
+
41
+ ```typescript
42
+ const RETRYABLE_ERRORS = new Set([
43
+ 'rate_limit_error',
44
+ 'overloaded_error',
45
+ 'api_error',
46
+ 'service_unavailable',
47
+ 'ECONNRESET',
48
+ 'ETIMEDOUT',
49
+ 'ENOTFOUND',
50
+ ]);
51
+
52
+ function isRetryable(error: unknown): boolean {
53
+ if (error instanceof ModelAPIError) {
54
+ // provider SDK specific
55
+ if (error.status === 429) return true; // Rate limit
56
+ if (error.status >= 500) return true; // Server error
57
+ return false; // Client errors (4xx except 429)
58
+ }
59
+
60
+ if (error instanceof Error) {
61
+ // Network errors
62
+ return RETRYABLE_ERRORS.has(error.name) ||
63
+ RETRYABLE_ERRORS.has((error as any).code);
64
+ }
65
+
66
+ return false;
67
+ }
68
+
69
+ async function callLLM(request: Request): Promise<Response> {
70
+ let lastError: Error;
71
+
72
+ for (let attempt = 0; attempt < 3; attempt++) {
73
+ try {
74
+ return await model.complete(request);
75
+ } catch (error) {
76
+ lastError = error as Error;
77
+
78
+ // Fail fast on permanent errors
79
+ if (!isRetryable(error)) {
80
+ console.error('[LLM] Non-retryable error:', error);
81
+ throw error;
82
+ }
83
+
84
+ // Retry with backoff for transient errors
85
+ const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
86
+ console.warn(`[LLM] Retryable error, waiting ${delay}ms:`, error);
87
+ await sleep(delay);
88
+ }
89
+ }
90
+
91
+ throw lastError!;
92
+ }
93
+ ```
94
+
95
+ **HTTP Status Code Guide:**
96
+
97
+ | Status | Meaning | Retry? |
98
+ |--------|---------|--------|
99
+ | 400 | Bad Request | No |
100
+ | 401 | Unauthorized | No |
101
+ | 403 | Forbidden | No |
102
+ | 404 | Not Found | No |
103
+ | 429 | Rate Limited | **Yes** |
104
+ | 500 | Server Error | **Yes** |
105
+ | 502 | Bad Gateway | **Yes** |
106
+ | 503 | Unavailable | **Yes** |
107
+ | 504 | Timeout | **Yes** |
@@ -0,0 +1,89 @@
1
+ ---
2
+ title: If Doubt, Upgrade the Model
3
+ impact: CRITICAL
4
+ impactDescription: Prevents quality degradation from incorrect model selection
5
+ tags: [routing, llm, cost-optimization, quality]
6
+ ---
7
+
8
+ ## If Doubt, Upgrade the Model
9
+
10
+ When the complexity classifier isn't confident, always upgrade to a more capable model. The cost of a wrong downgrade (poor response) exceeds the cost of unnecessary upgrade.
11
+
12
+ **The Rule:**
13
+
14
+ > **CRITICAL: If confidence < 0.6, ALWAYS upgrade to the next tier.**
15
+
16
+ **Incorrect:**
17
+
18
+ ```typescript
19
+ // Uses lower model even when uncertain
20
+ function selectModel(complexity: ComplexityResult): Model {
21
+ switch (complexity.level) {
22
+ case 'casual': return 'fast';
23
+ case 'task': return 'balanced';
24
+ case 'deep': return 'deep';
25
+ }
26
+ // BAD: No confidence check - may misroute
27
+ }
28
+ ```
29
+
30
+ **Correct:**
31
+
32
+ ```typescript
33
+ interface ComplexityResult {
34
+ level: 'casual' | 'task' | 'planning' | 'deep';
35
+ confidence: number; // 0-1
36
+ }
37
+
38
+ function selectModel(complexity: ComplexityResult): Model {
39
+ const { level, confidence } = complexity;
40
+
41
+ // If confidence is low, upgrade to be safe
42
+ if (confidence < 0.6) {
43
+ console.info(`[ROUTING] Low confidence (${confidence}), upgrading from ${level}`);
44
+ return upgradeModel(level);
45
+ }
46
+
47
+ switch (level) {
48
+ case 'casual': return 'fast';
49
+ case 'task':
50
+ case 'planning': return 'balanced';
51
+ case 'deep': return 'deep';
52
+ }
53
+ }
54
+
55
+ function upgradeModel(level: string): Model {
56
+ switch (level) {
57
+ case 'casual': return 'balanced'; // fast → balanced
58
+ case 'task': return 'balanced'; // stay balanced
59
+ case 'planning': return 'deep'; // balanced → deep
60
+ case 'deep': return 'deep'; // stay deep
61
+ default: return 'balanced'; // safe default
62
+ }
63
+ }
64
+ ```
65
+
66
+ **Cost Analysis:**
67
+
68
+ | Scenario | Model | Cost | Quality Risk |
69
+ |----------|-------|------|--------------|
70
+ | Confident casual → Fast | $0.005 | Low |
71
+ | Uncertain casual → Fast | $0.005 | **HIGH** (wrong model) |
72
+ | Uncertain casual → Balanced | $0.025 | Low |
73
+
74
+ **The Math:**
75
+
76
+ - Wrong model = user frustration + retry = 2x cost minimum
77
+ - Upgrade cost = 5x base (Balanced vs Fast)
78
+ - But: upgrade only when uncertain (~30% of cases)
79
+ - Net: slight cost increase, major quality improvement
80
+
81
+ **Detection Flow:**
82
+
83
+ ```
84
+ Message → detectWithRegex() → confidence ≥ 0.6?
85
+ ├─ YES → Use regex result
86
+ └─ NO → detectWithFastModel() → confidence ≥ 0.6?
87
+ ├─ YES → Use Fast result
88
+ └─ NO → UPGRADE MODEL
89
+ ```
@@ -0,0 +1,68 @@
1
+ ---
2
+ title: Model Cost Ratios
3
+ impact: HIGH
4
+ impactDescription: Essential reference for cost optimization decisions
5
+ tags: [routing, llm, cost-optimization, reference]
6
+ ---
7
+
8
+ ## Model Cost Ratios
9
+
10
+ Understanding cost ratios is essential for routing decisions.
11
+
12
+ **Cost ratios (illustrative orders of magnitude, verify current vendor prices):**
13
+
14
+ | Model | Input ($/M tokens) | Output ($/M tokens) | Ratio vs Balanced |
15
+ |-------|-------------------|---------------------|-----------------|
16
+ | Fast | $0.25 | $1.25 | **0.08x** |
17
+ | Balanced | $3.00 | $15.00 | 1.0x |
18
+ | Deep | $15.00 | $75.00 | 5.0x |
19
+
20
+ **Cost per typical request:**
21
+
22
+ | Model | Typical Request Cost | Use Case |
23
+ |-------|---------------------|----------|
24
+ | Fast | ~$0.005 | Greetings, simple Q&A, confirmations |
25
+ | Balanced | ~$0.025 | Standard tasks, analysis, coding |
26
+ | Deep | ~$0.10 | Complex reasoning, architecture, deep analysis |
27
+
28
+ **Key Insight:**
29
+
30
+ > Fast is **12x cheaper** than Balanced.
31
+ > Using Fast for 25% of traffic saves ~$0.015/request average.
32
+
33
+ **Incorrect:**
34
+
35
+ ```typescript
36
+ // No cost awareness
37
+ const model = 'mid-tier-model'; // Always mid-tier
38
+ ```
39
+
40
+ **Correct:**
41
+
42
+ ```typescript
43
+ const MODEL_COSTS = {
44
+ 'small-fast-model': { input: 0.25, output: 1.25 },
45
+ 'mid-tier-model': { input: 3.00, output: 15.00 },
46
+ 'top-tier-model': { input: 15.00, output: 75.00 },
47
+ } as const;
48
+
49
+ function estimateCost(model: string, inputTokens: number, outputTokens: number): number {
50
+ const costs = MODEL_COSTS[model];
51
+ return (inputTokens * costs.input + outputTokens * costs.output) / 1_000_000;
52
+ }
53
+
54
+ // Log costs for monitoring
55
+ console.info('[LLM] Request completed', {
56
+ model,
57
+ inputTokens: response.usage.input_tokens,
58
+ outputTokens: response.usage.output_tokens,
59
+ estimatedCost: estimateCost(model, response.usage.input_tokens, response.usage.output_tokens),
60
+ });
61
+ ```
62
+
63
+ **Cost Optimization Levers:**
64
+
65
+ 1. **Smart routing** → Use Fast when possible (-40% average)
66
+ 2. **Cache optimization** → Reduce input tokens (-60% on cached)
67
+ 3. **Output limits** → Constrain max_tokens where appropriate
68
+ 4. **Prompt engineering** → Shorter prompts, same quality