Skip to content

L1 → L2: Adding Feedback Loops

The L1→L2 transition adds automated feedback loops (strong types, tests, remediation-rich linters) so agents can verify and self-correct their work without human review.


What L1 looks like

The agent orients itself but cannot verify its output:

  • Weak or no type system (any everywhere)
  • Low or no test coverage
  • Linter rules that flag violations without remediation
  • Every output requires manual review

You are the feedback loop.

What L2 looks like

The agent validates most of its own work:

  • Strong types catch structural errors at write time
  • A test suite gives a binary "did I break anything?" signal
  • Linter rules carry remediation messages the agent can act on
  • Iteration runs through the Ralph Wiggum Loop: write → lint fails → fix → lint passes → tests fail → fix → tests pass

Exit criterion: the agent completes a scoped task (add a function with tests) and verifies its output without human review of mechanical errors.


Step 1: Enable a strong type system

Type errors fire at write time, at the exact location of the violation, with a specific message about what the type should be — the most actionable form of backpressure.

TypeScript strict mode:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,           // enables all strict checks
    "noImplicitAny": true,    // flags missing type annotations
    "strictNullChecks": true, // prevents null/undefined errors
    "noUncheckedIndexedAccess": true  // flags array access without null checks
  }
}

Enabling strict mode on an existing repo produces type errors. Fix them incrementally — they represent real bugs or assumptions agents would otherwise replicate.

Python:

# Add mypy to your dev dependencies
pip install mypy

# mypy.ini or pyproject.toml
[mypy]
strict = true
disallow_untyped_defs = true

Why agents benefit more: a human reasons from experience; an agent reads the message literally. Error specificity and location determine whether the agent self-corrects (Anthropic).

Step 2: Build test coverage on critical paths

A test suite gives a binary answer to "did I break anything?" Agents run tests, read failures, and fix — only if the suite exists.

Prioritize by agent risk, not business risk:

Path Why it matters Priority
Route handlers New features add or modify routes High
Service layer Layer boundary violations concentrate here High
Data access / repositories ORM and schema errors need integration tests High
Utilities and helpers Pattern replication — agents copy utilities (GitClear, 2025) Medium
Configuration loading Subtle misuse needs test assertions Medium

Cover the paths agents modify most before chasing edge cases.

Write assertions with informative failure output:

// Less useful for agent feedback
expect(result).toBeTruthy();

// More useful — agent reads the diff and knows exactly what changed
expect(result).toEqual({
  userId: '123',
  status: 'active',
  createdAt: expect.any(Date),
});

The more structured the assertion, the more specific the agent's fix.

Use integration tests over unit tests for agent-critical paths: they catch ORM misuse, transaction errors, and layer violations that mocked unit tests miss. LangChain's Terminal Bench gains came from structural verification, not mock-based unit tests (LangChain).

Step 3: Write linter rules with remediation messages

Standard linter rules flag violations; agent-useful rules explain the fix. The message enters context at the exact moment of the wrong decision — just-in-time delivery.

Good remediation message:

ERROR: Direct database imports are not allowed.
  Use repository classes from src/repositories/ instead.
  See src/repositories/user.repository.ts for an example.

Poor message, where the agent must infer the fix:

ERROR: Forbidden import from 'src/db/connection'.

Custom ESLint rule that enforces the repository pattern:

// eslint-rules/no-direct-db-import.js
module.exports = {
  create(context) {
    return {
      ImportDeclaration(node) {
        if (node.source.value.includes('src/db/connection')) {
          context.report({
            node,
            message:
              'Direct database imports are not allowed. ' +
              'Use repository classes from src/repositories/ instead. ' +
              'Example: import { UserRepository } from "../repositories/user.repository"',
          });
        }
      },
    };
  },
};

High-value rule targets:

Rule Prevents Remediation should say
No direct DB imports Layer violations "Use repository in src/repositories/"
No raw Error throw Inconsistent error handling "Use AppError subclasses from src/errors/"
No implicit any in new files Type coverage erosion "Annotate the parameter type explicitly"
No console.log in src/ Debug output in production "Use the logger from src/utils/logger.ts"
Import boundaries by directory Architecture violations "This layer cannot import from X; put shared code in Y"

Step 4: Add a pre-commit hook

A pre-commit hook gates agent output before it enters version history.

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: lint
        name: ESLint
        entry: npm run lint
        language: system
        types: [javascript, typescript]
      - id: type-check
        name: TypeScript
        entry: npm run type-check
        language: system
        pass_filenames: false

Or with Node tooling directly:

// package.json
{
  "scripts": {
    "lint": "eslint src/ --max-warnings 0",
    "type-check": "tsc --noEmit"
  }
}
# .husky/pre-commit (if using Husky)
npm run lint && npm run type-check

Commit → hook runs → on failure the commit is rejected with the error → agent reads, fixes, commits again. This is the Ralph Wiggum Loop at the commit boundary.

Step 5: Verify the transition

Exit check:

  1. Ask the agent to add a function to an existing service that calls a repository.
  2. Does it write code, run lint, read the error, and self-correct without intervention?
  3. Introduce a deliberate type error. Does the agent fix it?

If the agent still needs you to correct mechanical errors (wrong imports, types, error class), the loops are not tight enough. Typical causes: linter rules without remediation messages, too many any types, tests that miss the paths the agent modifies.

When this backfires

The L1→L2 transition pays off, but it is not free. Three conditions make it a poor investment:

  • Large existing any surface: strict mode on a codebase with hundreds of implicit any types floods unrelated files with errors. Fix cost dwarfs the agent benefit until most are annotated. Start with noImplicitAny scoped to new files, then expand incrementally.
  • High-churn paths with low test stability: if integration tests on agent-critical paths break often from schema or environment drift, agents learn to ignore failing tests rather than treat them as a backpressure signal. Stabilize the environment before you rely on tests as a feedback source.
  • Monorepos with shared strict config: enabling strict mode in one package cascades errors into shared libraries used elsewhere. Coordinate across package owners, or use path-scoped tsconfig overrides to limit how far the errors spread.

Key Takeaways

  • Agent autonomy scales with backpressure quality, not with model capability (Anthropic). A codebase with strict types and meaningful test coverage on critical paths supplies the backpressure that enables autonomous agent iteration. A codebase without them requires manual review of every output.
  • Linter messages are the best form of agent context: they fire at the exact moment and location of a violation. Write custom rules with actionable remediation messages.
  • Prioritize integration tests over mocked unit tests for agent-critical paths — the structural-verification finding LangChain reports from its Terminal Bench gains. They catch the errors agents actually make: ORM misuse, layer violations, transaction handling.
  • Pre-commit hooks are not optional. They are the gate that prevents the feedback loop from being bypassed. Without them, agents can commit and push non-compliant code that the Ralph Wiggum Loop would otherwise catch at the commit boundary.
  • Fix type errors incrementally. Enabling strict mode produces errors — this is expected. Each error fixed is a potential agent mistake prevented.
Feedback