Skip to content

Repository files navigation

Self-Healing AI — Agentic API Contract Demo

A demonstration of AI-driven agentic loops that keep a multi-project stack consistent with a shared data contract. When you modify the database schema, the system cascades updates through the API layer and into the UI — with developer approval at each step.

The pipeline is config-driven and goal-oriented, not hard-coded per change type. Nothing in the code says "when a field is added, do X." Instead, a declarative registry describes relationships (who consumes which contract, who owns which files, what invariant each must maintain), and a single generic loop reconciles the code to any contract change by reasoning toward that goal and verifying it.

How It Works

database-project/schema.json
        │  (contract)
        ▼
  Generic Agentic Loop  ── reconciles ──►  api-project/routes/persons.js   (LLM, goal-driven)
   (node: "api")         ── produces  ──►  api-project/swagger.json        (deterministic)
        │  (contract)
        ▼
  Generic Agentic Loop  ── reconciles ──►  ui-project/app/page.tsx         (LLM, goal-driven)
   (node: "ui")

Each participant is a node in contracts.config.json. A node declares only relationships:

  • consumes — the upstream contract file it watches
  • owns — the source files the reconciler may edit
  • goal — a one-line, natural-language invariant to maintain
  • verify — a command that must pass after a change (the safety net)
  • producer / produces — an optional deterministic step that regenerates a downstream contract

When a watched contract changes, the same generic loop runs for every node:

  1. Detect — a structural diff decides whether the contract actually changed (no field vocabulary baked in, so type changes, renames, and nested reshapes all count).
  2. Approve — the developer is prompted in the terminal.
  3. Reconcile — the LLM is handed the goal, the before/after contract, and the current file, and returns the updated file. It is not told what to do — it reasons toward the goal.
  4. Verify → retry → revert — the node's verify command runs. If it fails, the error is fed back and the model tries again (up to maxReconcileAttempts). If it still can't converge, every owned file is reverted. Verification, not a scripted prompt, is what keeps the change safe.
  5. Produce — on success, any deterministic producer regenerates the downstream contract (e.g. swagger.json), which cascades to the next node.

Deterministic contract production stays deterministic (Swagger is generated by a pure function); the LLM only reconciles owned source. Adding a new consumer to the pipeline is a matter of adding a node to the registry — no new orchestration code.

Adding a new node

Append an entry to contracts.config.json, for example a second consumer of the Swagger contract:

{
  "id": "sdk",
  "project": "SDK_Project",
  "consumes": "api-project/swagger.json",
  "contractPath": ["components", "schemas", "Person", "properties"],
  "owns": ["sdk-project/src/person.ts"],
  "goal": "The Person interface in person.ts must have one property per field in the OpenAPI Person schema, with matching types.",
  "verify": "npm run --workspace=sdk-project typecheck",
  "maxReconcileAttempts": 2
}

Then start it with the generic loop (three lines, mirroring api-project/agentic-loop.js):

const { getNode } = require('./shared/registry');
const { startNode } = require('./shared/agentic-loop');
startNode(getNode('sdk'));

Project Structure

├── contracts.config.json # Declarative registry: the pipeline's nodes and their relationships
├── database-project/     # NeDB database with schema definition
│   ├── schema.json       # Source of truth for data shape (the root contract)
│   ├── db.js             # Database access layer
│   └── seed.js           # Seeds sample data
├── api-project/          # Express REST API
│   ├── server.js         # API server
│   ├── routes/           # Route handlers (owned + reconciled by the "api" node)
│   ├── swagger.json      # OpenAPI spec (deterministically produced from schema.json)
│   ├── swagger-generator.js # Pure schema → OpenAPI generator (the "api" node's producer)
│   └── agentic-loop.js   # Thin entry point: startNode(getNode('api'))
├── ui-project/           # Next.js React UI
│   ├── components/       # PersonTable component
│   ├── app/page.tsx      # Table page (owned + reconciled by the "ui" node)
│   └── agentic-loop.js   # Thin entry point: startNode(getNode('ui'))
├── shared/               # The generic engine (project-agnostic)
│   ├── registry.js       # Loads contracts.config.json, resolves nodes
│   ├── agentic-loop.js   # Generic watch → detect → approve → reconcile → produce loop
│   ├── reconcile.js      # Generic goal-driven reconcile + verify + retry + revert
│   ├── contract-diff.js  # Generic structural contract diff / change detection
│   ├── approval-prompt.js# Terminal y/n approval
│   ├── llm-client.js     # OpenAI-compatible LLM client
│   ├── file-watcher.js   # File change detection (chokidar)
│   └── notifier.js       # macOS notifications
├── scripts/              # Helper scripts
│   ├── start-demo.js     # Starts all services
│   ├── add-field.js      # Adds a field to schema + generates data
│   └── remove-field.js   # Removes a field from schema + data
└── tests/                # Integration tests

Prerequisites

Setup

# Clone and install dependencies
git clone <repo-url>
cd selfHealingAI
npm install

# Configure environment
cp .env.example .env
# Edit .env with your LLM server address and model name

# Seed the database with sample data
npm run --workspace=database-project seed

Running the Demo

# Start the agentic loops (watches for changes)
npm run demo

# In a separate terminal, start the API server
npm run start:api

# In a separate terminal, start the Next.js UI
cd ui-project && npm run dev

Triggering a Cascade

Add a field to the schema:

# Adds a field and generates sample data via LLM
node scripts/add-field.js date_of_birth string
node scripts/add-field.js age number

Remove a field:

node scripts/remove-field.js date_of_birth

After a schema change, each node's loop will:

  1. Detect that the contract actually changed
  2. Prompt for approval in the terminal (y/n)
  3. Reconcile its owned files toward the node's goal via the LLM
  4. Run the node's verify command; retry with the failure fed back, or revert if it can't converge
  5. On success, regenerate any downstream contract — cascading to the next node

Configuration

Variable Default Description
LLM_ENDPOINT http://localhost:8080/v1/chat/completions OpenAI-compatible completions URL
LLM_MODEL default Model name to pass to the LLM server
PORT 3000 API server port

Running Tests

# Run all tests (unit + property-based)
npm test

# Run UI tests only
npm test --workspace=ui-project

License

ISC

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages