Skip to content

AI & the governed agent

@samabaasi/ai is the AI layer: natural language → app.json and a governed agent that acts via OData tools. Both are built on one principle — the model proposes, the platform disposes: AI output is validated/gated by code we wrote before anything happens.

1. NL → app.json

Describe an app (or an edit) in English; get a validated app.json back.

ts
import { generateAppJson, createAnthropicClient } from '@samabaasi/ai';

const result = await generateAppJson({
  prompt: 'a products grid with a price column and a low-stock email workflow',
  current,                       // optional: editing an existing app.json
  llm: createAnthropicClient()   // Claude (claude-opus-4-8); MockLlmClient in tests
});
// result.app  — validated AppDefinition
// result.diff — a coarse diff vs `current`, for preview

How it stays correct

  • Zod validate‑and‑retry. The model proposes JSON → @samabaasi/core validates → on failure the exact Zod issues are fed back and it retries (up to N) → ConfiguratorError if irreducible. The schema is the source of truth, not the model.
  • Dry run, never applied. It returns { app, diff } for review; applying is a separate, explicit PUT. Exposed at POST /api/config/natural (501 without a key, 422 on irreducible failure).
  • Why not "structured outputs"? The app.json schema is recursive (forms, layout), which json_schema output can't enforce — so the Zod loop is the enforcement.

Fast/cheap for AI: parsing is already O(n) — not the bottleneck. The real wins are JSON‑Patch edits (send only the delta, O(changes) tokens), prompt‑caching the schema guide, and minimizing retries. Editing via patch turns "regenerate the whole document" into "send the change."

2. The governed agent

The agent achieves a goal by calling tools derived from your OData $metadata — but every call is gated.

Tool catalog (from $metadata)

buildToolCatalog(model) turns entities + operations into typed, classified tools:

SourceToolsClassification
Entityquery / getread
Entitycreate / updatewrite
Entitydeletedestructive
Functionfunction_*read
Actionaction_*write — or destructive by name (Discontinue/Delete/Cancel/Refund…)

Each tool carries a Zod argument schema + an OData operation descriptor.

The safety layer (PA5)

A GovernedToolset is the gate every proposed call passes:

proposed call ──▶ allow-list (default deny)
              ──▶ Zod arg validation
              ──▶ iteration cap
              ──▶ classification gate:
                     read         → run now
                     write        → REQUIRES human approval
                     destructive  → REQUIRES approval (always, even if writes auto-approve)
              ──▶ audit (every decision + execution + approver)
ts
import { buildToolCatalog, GovernedToolset, runAgent, resumeAgent } from '@samabaasi/ai';

const toolset = new GovernedToolset(buildToolCatalog(model), { allow: '*' }, { executor });

const r = await runAgent('Find low-stock products and discontinue obsolete ones', { llm, toolset, catalog });
// reads ran; a discontinue (destructive) is PAUSED:
if (r.status === 'awaiting_approval') {
  await resumeAgent(r.transcript!, r.pending!, { approve: true, approver: 'mgr@acme.com' }, opts);
}

The loop has no path around the gate: reads execute and feed observations back; writes/destructive pause with a resumable transcript; disallowed/invalid calls are denied (and fed back so the model adapts); a step cap bounds it. runAgent/resumeAgent mirror the workflow approval flow.

Why this answers "AI apps are insecure"

  • The agent cannot make an unapproved write — it's structurally impossible, not a prompt instruction.
  • Prompt injection via OData data can't escalate: data is data, writes still need a human, and the tool allow‑list is default‑deny.
  • Credentials never enter the model — they're injected at egress from the vault.
  • Full audit of every proposal/decision/execution.

Models: claude-opus-4-8 for generation/planning, claude-haiku-4-5 for cheap classification (configurable). See the Security model for the complete threat treatment.

Released under the MIT License.