Skip to main content

Self-Healing Structured Outputs and Saga-Backed Tool Calls for LLMs with @siglo/ai

Shipping an LLM feature in a prototype takes 20 lines of fetch(). Shipping that same LLM feature in a transactional backend service immediately hits four reliability walls:
  1. Malformed Structured Outputs: Even with JSON mode enabled, models occasionally violate domain constraints (e.g., returning a negative refund amount or missing a required enum field). If you just call JSON.parse(), your request crashes.
  2. Provider Outages & Rate Limits: When Anthropic returns a 529 Overloaded or OpenAI hits a 429 rate limit, your workflow needs to fail over cleanly to a secondary provider without losing prompt context or blowing past your request deadline.
  3. Uncompensated Tool Side Effects: When an LLM agent calls two tools in sequence—say, reserveInventory followed by issueRefund—and then fails on the final step, standard AI SDKs leave the first tool’s database mutation dangling in production.
  4. SSRF & Unrestricted Network Egress: Giving an LLM agent arbitrary tool-execution capabilities without step-level network egress policies is a security incident waiting to happen.
We built @siglo/ai on top of the Siglo workflow runtime to solve these exact failure modes: turning LLM calls and agentic tool loops into self-healing, Saga-compensated, and network-sandboxed workflow steps.

1. Self-Healing Structured Outputs (maxRepairAttempts + Standard Schema)

@siglo/ai accepts any validator that implements the Standard Schema (StandardSchemaV1) specification—including Zod, Valibot, and ArkType. When you pass a schema to aiStep(), two things happen automatically:
  1. Preflight Native Schema Enforcement: Before sending the request, @siglo/ai extracts the JSON Schema to enforce native structured outputs on providers that support it (OpenAI, Anthropic, Gemini, Ollama).
  2. Bounded Self-Healing Repair Loop: When the model responds, @siglo/ai parses the JSON and validates it against your StandardSchemaV1 validator. If validation fails and repairs < maxRepairAttempts, @siglo/ai does not throw immediately—it appends the assistant’s invalid output and the exact schema validation diagnostics back into the conversation history:
The model repairs its own JSON in the next turn, and your downstream workflow step receives a statically typed, guaranteed-valid TypeScript object—plus full token and cost telemetry under $ai:
If all maxRepairAttempts are exhausted, @siglo/ai throws a typed AiOutputValidationError containing the structured validation diagnostics.

2. Step-as-Tool: Bringing LIFO Saga Rollbacks to LLM Tool Calls (defineAiTool)

In most AI frameworks, a “tool” is just an arbitrary async callback. If an LLM calls a tool that mutates state (creates a Jira ticket, reserves stock, or issues a credit) and a subsequent step in the workflow fails, you have no built-in way to roll back the tool’s side effects. In @siglo/ai, every AI tool is a full @siglo/core Step. When you wrap a Step with defineAiTool():
  1. Batch Pre-Validation Before Side Effects: If the model emits multiple tool calls in a single turn, @siglo/ai validates the tool names, call IDs, and inputSchema payloads for the entire batch before executing the first side effect (packages/ai/src/step.ts).
  2. Parent Saga Registration: Each tool executes via Step.start(tool.step, input, { stackId, ... }), binding its .compensate() handler directly to the parent workflow’s execution stack.
Notice the safety invariant enforced by AiStep: if an aiStep is configured with tools, @siglo/ai explicitly forbids step-level .cache(), .onFail() retries, or .idempotent() replay on the outer aiStep (TypeError: AI steps with tools cannot use caching, retries, failure recovery or idempotent replay), preventing accidental duplicate tool executions while keeping individual tool steps and parent Sagas in full control.

3. Automatic Zero-Trust Network Egress Policies

When you instantiate an AiStep, it automatically computes a least-privilege network egress allowlist (this.allowNetwork(...)) consisting only of:
  • The hostnames of the configured primary model and fallbackModels (e.g., api.anthropic.com, api.openai.com).
  • Any hosts explicitly allowed by the attached tools (tool.step.networkPolicyConfig?.allowedHosts).
When combined with Step.installNetworkInterceptors(), neither the AI step nor any tool invoked during the agent loop can make unauthorized outbound requests to internal metadata services (169.254.169.254), private VPC subnets, or unapproved third-party domains.

4. Built-In Token Cost & Latency Telemetry ($ai)

Every aiStep returns an AiStepResult<TOutput> that merges your parsed schema output alongside a non-enumerable $ai metrics summary (and emits OpenTelemetry GenAI semantic conventions plus live @siglo/monitor inspection cards):

Try @siglo/ai