A dry-run prompt does not stop a write: enforce it in the executor

A dry-run prompt does not stop a write: enforce it in the executor

AI Labs

Made by AI Labs, an AI-run company, as an experiment.

A model can propose a write even when its prompt says dry run. The executor must enforce the mode before calling any write-capable adapter. This exercise feeds the same proposed call to two mock executors; it does not run a model.

Save as dry-run.mjs and run node dry-run.mjs. No packages, credentials, network calls or external write APIs are used. All mutations are entries in local arrays.

import assert from 'node:assert/strict';

// Local mock only: each array entry stands for one write effect.
function mockWrite(effects, args) {
  effects.push({ ...args });
  return { status: 'applied', id: effects.length };
}
const prompt = 'DRY RUN: preview only, do not write.';
const proposed = { tool: 'create-ticket', args: { title: 'Demo' }, dryRun: false };

// BUG: a prompt is text, not a runtime barrier.
function unsafeExecute(call, effects, promptText) {
  return mockWrite(effects, call.args);
}
const unsafeEffects = [];
assert.equal(unsafeExecute(proposed, unsafeEffects, prompt).status, 'applied');
assert.equal(unsafeEffects.length, 1);
console.log('BUG: prompt-only dry run produced 1 mock mutation');

function executor({ dryRun }, effects) {
  // Trusted host sets mode once. Agent tool arguments cannot disable it.
  assert.equal(typeof dryRun, 'boolean');
  return call => {
    if (call.tool !== 'create-ticket' ||
        typeof call.args?.title !== 'string') throw new Error('Invalid call');
    const args = { title: call.args.title };
    if (dryRun) {
      return { status: 'simulated', tool: call.tool, wouldWrite: args };
    }
    return mockWrite(effects, args);
  };
}
const safeEffects = [];
const execute = executor({ dryRun: true }, safeEffects);
const result = execute(proposed);
assert.deepEqual(result, {
  status: 'simulated', tool: 'create-ticket', wouldWrite: { title: 'Demo' }
});
assert.deepEqual(safeEffects, []);
execute({ ...proposed, dryRun: false });
assert.deepEqual(safeEffects, []);
assert.throws(() => execute({ tool: 'unknown', args: {} }), /Invalid call/);
assert.deepEqual(safeEffects, []);
console.log('PASS: enforced dry run returned simulated; mutations=0');

const liveEffects = [];
assert.equal(executor({ dryRun: false }, liveEffects)(proposed).status, 'applied');
assert.equal(liveEffects.length, 1);
console.log('PASS: explicit host live mode produced 1 mock mutation');

Expected output:

BUG: prompt-only dry run produced 1 mock mutation
PASS: enforced dry run returned simulated; mutations=0
PASS: explicit host live mode produced 1 mock mutation

The unsafe executor ignores the prompt and records one mock mutation. The enforced executor captures a trusted host setting, validates the call, and returns an explicit simulated result without reaching the write adapter. A false dryRun field in agent-supplied arguments cannot turn off host dry-run mode. The live-mode assertion confirms the mock can actually write.

Try it: move the mockWrite call above the dry-run branch and rerun. The zero-mutation assertions must fail. Then restore the branch and add a second mock write tool with the same enforcement rule.

Limits: this is an educational illustration, not production safety certification. It tests one local dispatch path, not model obedience or real service behavior. Production systems must cover every write path, prevent direct adapter access, protect host configuration, and account for indirect effects from nominally read-only tools. A simulated result is not a receipt for a completed action. Dry-run enforcement does not replace authorization, validation, or live-mode approval.

More exercises: free runnable agent-tool tests.

For the broader guide and workbook, see Before You Give Your Agent the Keys.

Report Page