Two lines put a check at every step your agent takes, and Provely fixes what it can.

Coverage is not yet complete. Being updated continuously.

Fixed on every path where it appliesMeasured, not fixed everywhereFails on at least one pathNot measured yet

Step 1. Why does the plain code break?

The model sends get_weather({'city': 'Paris'}). The model writes the arguments of get_weather with single quotes, so they are not JSON.

typescript/weather.ts, the plain file
import OpenAI from 'openai';

const getWeather = ({ city }: { city: string }) => `Sunny in ${city}`;
const parameters = { type: 'object', properties: { city: { type: 'string' } } };

const reply = await new OpenAI().chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
  tools: [{ type: 'function', function: { name: 'get_weather', parameters } }],
});
const call = reply.choices[0]!.message.tool_calls![0]!;
console.log(getWeather(JSON.parse(call.function.arguments)));
The run
$ node weather.ts
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
exit 1

Step 2. What does a fix by hand take?

A try/catch adds 5 lines of code. It catches the error, and the run gives no answer:

The run with the try/catch
$ node weather-defended.ts
Could not read the tool arguments: Expected property name or '}' in JSON at position 1 (line 1 column 2)
exit 0

A real repair adds 28 lines of code, 34 with the comments. Each comment states what its guard does.

typescript/weather-repaired.ts, fixed by hand
import OpenAI from 'openai';

const getWeather = ({ city }: { city: string }) => `Sunny in ${city}`;
const parameters = { type: 'object', properties: { city: { type: 'string' } } };

function repairJson(text: string): string {
  // the model may wrap the JSON in a Markdown fence
  const fence = text.trim().match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
  const source = fence ? fence[1]! : text;
  let out = '';
  let quote: string | null = null;
  for (let i = 0; i < source.length; i += 1) {
    const char = source[i]!;
    if (quote === null) {
      // the model may use single quotes: every string gets double quotes
      if (char === "'" || char === '"') quote = char;
      out += quote === null ? char : '"';
    } else if (char === '\\') {
      // an escaped single quote is not valid JSON: keep the quote, drop the backslash
      const next = source[i + 1] ?? '';
      out += next === "'" ? "'" : char + next;
      i += 1;
    } else if (char === quote) {
      quote = null;
      out += '"';
    } else {
      // a double quote inside a single-quoted string must be escaped
      out += char === '"' ? '\\"' : char;
    }
  }
  return out;
}

const reply = await new OpenAI().chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
  tools: [{ type: 'function', function: { name: 'get_weather', parameters } }],
});
const call = reply.choices[0]!.message.tool_calls![0]!;
// the model may send arguments that are not JSON: tell the user, do not crash
try {
  console.log(getWeather(JSON.parse(repairJson(call.function.arguments))));
} catch (error) {
  console.log(`Could not read the tool arguments: ${(error as Error).message}`);
}
The run
$ node weather-repaired.ts
Sunny in Paris
exit 0

Fixed by hand, for 2 kinds of fault.

Step 3. What do the two lines of Provely do?

Add 2 lines to the plain file: lines 2 and 3. Provely fixes the call before your code reads it.

typescript/weather.ts, with Provely
import OpenAI from 'openai';
import { Provely } from '@provely/sdk';
new Provely();

const getWeather = ({ city }: { city: string }) => `Sunny in ${city}`;
const parameters = { type: 'object', properties: { city: { type: 'string' } } };

const reply = await new OpenAI().chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
  tools: [{ type: 'function', function: { name: 'get_weather', parameters } }],
});
const call = reply.choices[0]!.message.tool_calls![0]!;
console.log(getWeather(JSON.parse(call.function.arguments)));
The run
$ node weather.ts
Sunny in Paris
exit 0

Auto fixed by Provely: repair_in_place · single_quoted_strings · get_weather

Lines of code: 0 → breaks · 5 → caught, no answer · 28 → fixes 2 kinds of fault · 2 → Provely's checks

Recorded on 2026-10-17. Every output above is the output of that run.

How does Provely handle a multi-hop agent run?

The askModelanswersCheck theanswerTool runs:get_chargeCheck theresultModelanswersCheck theanswerTool runs:refund_chargeCheck theresultFinalanswerRun memory: completed writes, step order, countsA repeat of a completed write is refused
Two checks on every hop, and one memory for the whole run.
In words
  • The ask goes to the model, and the model answers with a tool call.
  • Provely checks the answer before the framework runs the call.
  • The tool runs, and Provely checks the result before the model reads it.
  • The model calls the next tool, and the same two checks run again, until the final answer.
  • One run memory spans every hop: completed writes, the step order and the counts.

A multi-hop run is a loop. The model answers, a tool runs, the result goes back to the model, and the model calls the next tool, until the final answer. Provely checks every hop at two points.

  • The model's answer, before your framework runs the call. Provely repairs broken arguments. It refuses an invented tool or value, or it returns the error to the model so the model can correct it. It stops a tool that hangs.
  • The tool's result, before the model reads it. Provely catches an error page that says "succeeded", a replayed result and a broken value. The model does not build its next step on a false result.
  • One memory for the whole run. Provely remembers each completed write, so the same refund later in the same run is refused. A signed rule can set the order of steps (look up the charge before the refund) and a limit (at most 2 refunds in a run).
  • More than one agent. Each agent in a run keeps its own plan and its own list of tools.

Not covered yet: a retry across separate agent calls; pauses for human approval; resuming from a checkpoint; parallel runs; durable workflow engines. The coverage page shows what is measured. The intent firewall will check that a whole workflow reached its goal in the real systems.

What comes next?

Coming soon. The intent firewall is coming soon.

It will check that what an agent did matches what a person authorised, in the real systems, with a signed receipt. Read the planned intent firewall.