|
| 1 | +/** |
| 2 | + * failure-pattern-demo.ts |
| 3 | + * |
| 4 | + * Simulates the 4-step failure pattern that capable agents fall into: |
| 5 | + * 1. Incomplete context |
| 6 | + * 2. Locally reasonable changes |
| 7 | + * 3. No global verification |
| 8 | + * 4. Premature completion |
| 9 | + * |
| 10 | + * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts |
| 11 | + */ |
| 12 | + |
| 13 | +// --------------------------------------------------------------------------- |
| 14 | +// Types |
| 15 | +// --------------------------------------------------------------------------- |
| 16 | + |
| 17 | +interface StepState { |
| 18 | + step: number; |
| 19 | + name: string; |
| 20 | + contextAvailable: string[]; |
| 21 | + contextMissing: string[]; |
| 22 | + actionTaken: string; |
| 23 | + localOutcome: string; |
| 24 | + globalImpact: string; |
| 25 | + completed: boolean; |
| 26 | +} |
| 27 | + |
| 28 | +// --------------------------------------------------------------------------- |
| 29 | +// Simulated "model" -- a simple decision function that bases its output |
| 30 | +// solely on whatever context it has been given. |
| 31 | +// --------------------------------------------------------------------------- |
| 32 | + |
| 33 | +function modelDecide(context: string[], task: string): string { |
| 34 | + const has = (s: string) => context.some((c) => c.includes(s)); |
| 35 | + |
| 36 | + // The task is to "add a search endpoint to the API". |
| 37 | + // Correct answer requires knowing about auth middleware and rate limiting. |
| 38 | + |
| 39 | + if (!has("auth")) { |
| 40 | + return "Created new route handler /search without authentication checks"; |
| 41 | + } |
| 42 | + if (!has("rate-limit")) { |
| 43 | + return "Added search route with auth but forgot rate limiting"; |
| 44 | + } |
| 45 | + if (!has("test-standards")) { |
| 46 | + return "Implemented search with auth and rate-limit, but no tests"; |
| 47 | + } |
| 48 | + return "Fully implemented search endpoint with auth, rate-limit, and tests"; |
| 49 | +} |
| 50 | + |
| 51 | +// --------------------------------------------------------------------------- |
| 52 | +// Failure simulation |
| 53 | +// --------------------------------------------------------------------------- |
| 54 | + |
| 55 | +function simulateFailurePattern(): StepState[] { |
| 56 | + const steps: StepState[] = []; |
| 57 | + |
| 58 | + // ---- Step 1: Incomplete Context ---- |
| 59 | + const step1Context = ["project structure", "route definitions"]; |
| 60 | + const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; |
| 61 | + const step1Decision = modelDecide(step1Context, "add search endpoint"); |
| 62 | + |
| 63 | + steps.push({ |
| 64 | + step: 1, |
| 65 | + name: "Incomplete Context", |
| 66 | + contextAvailable: step1Context, |
| 67 | + contextMissing: step1Missing, |
| 68 | + actionTaken: step1Decision, |
| 69 | + localOutcome: "Looks good -- route compiles, returns data", |
| 70 | + globalImpact: "Missing auth means unauthenticated access to search", |
| 71 | + completed: false, |
| 72 | + }); |
| 73 | + |
| 74 | + // ---- Step 2: Locally Reasonable Changes ---- |
| 75 | + // The agent adds auth after a hint, but still lacks other context. |
| 76 | + const step2Context = [...step1Context, "auth middleware"]; |
| 77 | + const step2Missing = ["rate-limiting policy", "test standards"]; |
| 78 | + const step2Decision = modelDecide(step2Context, "add search endpoint"); |
| 79 | + |
| 80 | + steps.push({ |
| 81 | + step: 2, |
| 82 | + name: "Locally Reasonable Changes", |
| 83 | + contextAvailable: step2Context, |
| 84 | + contextMissing: step2Missing, |
| 85 | + actionTaken: step2Decision, |
| 86 | + localOutcome: "Route has auth -- looks complete locally", |
| 87 | + globalImpact: "No rate limiting means the endpoint can be abused", |
| 88 | + completed: false, |
| 89 | + }); |
| 90 | + |
| 91 | + // ---- Step 3: No Global Verification ---- |
| 92 | + const step3Context = [...step2Context, "rate-limiting policy"]; |
| 93 | + const step3Missing = ["test standards"]; |
| 94 | + const step3Decision = modelDecide(step3Context, "add search endpoint"); |
| 95 | + |
| 96 | + steps.push({ |
| 97 | + step: 3, |
| 98 | + name: "No Global Verification", |
| 99 | + contextAvailable: step3Context, |
| 100 | + contextMissing: step3Missing, |
| 101 | + actionTaken: step3Decision, |
| 102 | + localOutcome: "Feature appears fully implemented", |
| 103 | + globalImpact: "No tests -- regression risk, violates project standards", |
| 104 | + completed: false, |
| 105 | + }); |
| 106 | + |
| 107 | + // ---- Step 4: Premature Completion ---- |
| 108 | + steps.push({ |
| 109 | + step: 4, |
| 110 | + name: "Premature Completion", |
| 111 | + contextAvailable: step3Context, |
| 112 | + contextMissing: step3Missing, |
| 113 | + actionTaken: 'Agent outputs: "Done. Added search endpoint."', |
| 114 | + localOutcome: "Agent is satisfied, task marked complete", |
| 115 | + globalImpact: "Task is incomplete -- missing tests, no E2E verification", |
| 116 | + completed: true, |
| 117 | + }); |
| 118 | + |
| 119 | + return steps; |
| 120 | +} |
| 121 | + |
| 122 | +// --------------------------------------------------------------------------- |
| 123 | +// Comparison table |
| 124 | +// --------------------------------------------------------------------------- |
| 125 | + |
| 126 | +function printComparisonTable(steps: StepState[]): void { |
| 127 | + console.log("\n" + "=".repeat(90)); |
| 128 | + console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); |
| 129 | + console.log("=".repeat(90)); |
| 130 | + |
| 131 | + for (const s of steps) { |
| 132 | + console.log(`\n Step ${s.step}: ${s.name}`); |
| 133 | + console.log(" " + "-".repeat(60)); |
| 134 | + console.log(` Context available : ${s.contextAvailable.join(", ")}`); |
| 135 | + console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); |
| 136 | + console.log(` Action taken : ${s.actionTaken}`); |
| 137 | + console.log(` Local outcome : ${s.localOutcome}`); |
| 138 | + console.log(` Global impact : ${s.globalImpact}`); |
| 139 | + console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); |
| 140 | + } |
| 141 | + |
| 142 | + // Summary comparison |
| 143 | + console.log("\n" + "=".repeat(90)); |
| 144 | + console.log(" COMPARISON: What the agent saw vs. what was actually needed"); |
| 145 | + console.log("=".repeat(90)); |
| 146 | + |
| 147 | + const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; |
| 148 | + const finalAvailable = steps[steps.length - 1].contextAvailable; |
| 149 | + |
| 150 | + const header = "| Criterion | Available | Missing | Status |"; |
| 151 | + const sep = "|------------------------|-----------|---------|----------|"; |
| 152 | + console.log("\n" + header); |
| 153 | + console.log(sep); |
| 154 | + |
| 155 | + for (const item of totalRequired) { |
| 156 | + const avail = finalAvailable.includes(item); |
| 157 | + const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; |
| 158 | + console.log(row); |
| 159 | + } |
| 160 | + |
| 161 | + const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; |
| 162 | + console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); |
| 163 | + console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); |
| 164 | +} |
| 165 | + |
| 166 | +// --------------------------------------------------------------------------- |
| 167 | +// Run |
| 168 | +// --------------------------------------------------------------------------- |
| 169 | + |
| 170 | +printComparisonTable(simulateFailurePattern()); |
0 commit comments