Veefore docs

Manual setup

Every line Veefore needs in your codebase, written by hand — what to add, where it goes, and how to tell which shape your code is.

veefore integrate writes these changes for you. This page is for teams that will not let a tool edit their source: it lists every line Veefore needs, where each one goes, and how to decide which shape applies to your code.

Nothing here is a shortcut. If you add what this page describes, the result is identical to what the automatic rewrite produces.

#The three ways to get the code in

Route What happens When to use it
Veefore AI veefore integrate sends the region of a file that needs changing to a model Veefore hosts, shows you the diff, writes nothing until you accept You want it done and are comfortable with code leaving your machine to Veefore and its model provider
Your own API key The same, except the request goes directly from your machine to Anthropic or OpenAI with your key. Code never reaches Veefore You want AI help but your code may only go to a provider you already have an agreement with
By hand This page. The CLI prints the same instructions with your real file and line numbers Review requirements, air-gapped work, or you simply want to know what is being added

Either AI route shows a diff first, keeps a backup, and rolls back if your own checks newly fail. Pick the route at the prompt, or force it:

bash
veefore integrate --assist=manual   # print instructions, change nothing
veefore integrate --assist=own      # your Anthropic or OpenAI key
veefore integrate --assist=veefore  # Veefore-hosted model
veefore integrate --assist=off      # skip the offer entirely
veefore integrate --check           # verify every tool is protected, change nothing

--assist=manual is worth running even if you intend to type everything yourself: it names your files and lines rather than leaving you to map this page onto your project.

#What you are adding, in one sentence each

Line What it does
guard('tool_key', fn) Wraps a tool's function. Veefore decides before the function runs
await checkpoint('tool_key', args) A gate with nothing to wrap. Veefore decides before the next statement runs
const notice = approvalNotice(error) Turns an approval that was raised into a sentence for the user
await resumeApprovals() Finishes actions a human approved since the last turn
{ onApproval: 'defer' } Return immediately instead of blocking when a human must approve

#Step 1 — install and configure

bash
npm install @veefore/sdk          # or: pip install veefore-py

veefore init writes .env.local. By hand, it needs:

bash
VEEFORE_GATEWAY_URL=https://gateway.veefore.com
VEEFORE_AGENT_ID=agt_…
VEEFORE_AGENT_KEY_ID=key_…
VEEFORE_AGENT_PRIVATE_KEY=…        # generated locally; Veefore never receives it
VEEFORE_APPROVAL_MODE=defer        # wait | defer | throw — see step 3

The private key is generated on your machine and written to that file only. Veefore cannot recover it. Move it into your secret manager before deploying.

#Step 2 — protect each tool

Which line you add depends on the shape of your code. Work through these in order; the first one that matches is the one to use.

#Shape A — the tool is a function you can wrap

ts
// before
export const refundOrder = async ({ orderId }) => {
  return await payments.refund(orderId);
};

// after
import { guard } from '@veefore/sdk';

export const refundOrder = guard('refund_order', async ({ orderId }) => {
  return await payments.refund(orderId);
});

guard() returns a promise, so the wrapped function must be async. If it was not, make it so.

A method becomes a property:

ts
// before:  async execute(args) { … }
// after:
execute: guard('refund_order', async (args) => { … }),

Python uses a decorator, directly above the def and below any framework decorator:

python
from veefore import guard

@tool
@guard("refund_order")
async def refund_order(order_id: str) -> dict:
    return await payments.refund(order_id)

#Shape B — tools are data, and one place runs whichever the model picked

Add exactly one guard, around the call that performs the chosen tool, keyed by the expression holding its name. Not one guard per tool.

ts
// before
const result = await executor.execute(call);

// after
const result = await guard(call.tool, (_args: typeof call.params) =>
  executor.execute(call),
)(call.params);

call.params matters: that is what your policies read. Pass the property holding the tool's own arguments, never the whole call object when such a property exists.

#Shape C — the tool runs inside a branch, with no function to wrap

This is the common shape in an agent loop, and the one most people get wrong. There is nothing to wrap, so use checkpoint() as the first statement of the block that performs the action:

ts
import { checkpoint } from '@veefore/sdk';

if (name === 'delete_post') {
  await checkpoint('delete_post', args);
  await storage.deleteContent(contentId);
  return { ok: true, message: 'Post deleted.' };
}

Put it where the action happens, not where it is described

Agents often branch on the same tool key twice — once to build a confirmation card for the user, once to carry the action out:

ts
if (name === 'delete_post') {          // builds a card. NOT here.
  return { card: { action: 'delete_post' } };
}
…
if (name === 'delete_post') {          // deletes. HERE.
  await checkpoint('delete_post', args);
  await storage.deleteContent(contentId);
}

A gate on the card can never cause a deletion however many times it is approved, because checkpoint() completes by being reached again. Worse, the real deletion then runs unchecked. Find the branch that performs the side effect — the one that awaits a write — and gate that one.

#Shape D — the tool is on an MCP server or another agent

Nothing to wrap. The bridge call is itself the protected action:

ts
const outcome = await client.mcpCall({
  server: 'srv_files',
  tool: 'delete_file',
  arguments: { path },
});

Approvals work the same as for guard(); see step 3.

#Step 3 — choose what happens when a human must approve

One setting, three behaviours. Set it once in VEEFORE_APPROVAL_MODE, or per call.

Mode Behaviour Use for
wait Block until the decision lands, then run the action Batch jobs, queue workers, anything with no user waiting
defer Return immediately with a sentence for the user; finish on a later turn Chat agents. A turn times out in seconds; a reviewer does not
throw Raise and do not perform the action You want approvals to be a hard failure

wait is the default. If your agent talks to a person, you want defer:

bash
VEEFORE_APPROVAL_MODE=defer

Per call, if you need to differ from the project setting:

ts
guard('refund_order', refundOrder, { onApproval: 'defer' })
await checkpoint('delete_post', args, { onApproval: 'defer' })
await client.mcpCall({ … }, { onApproval: 'defer' })

Python: on_approval="defer".

Deferring is two halves

Returning immediately is half of it. Collecting the decision later is the other, and the SDK cannot add it for you — it is in your turn loop, which is framework-specific and usually nowhere near a tool. A project with the first half and not the second is in the worst state Veefore can put it in: the reviewer approves, the agent promised the user it would happen, and nothing does. veefore doctor fails until both halves are present.

#Step 4 — the two lines in your turn loop

These are the lines no automatic rewrite will add for you.

#4a — tell the user, instead of reporting a failure

A checkpoint() raises when a human must approve. Agent code normally catches everything and reports it as "something went wrong", so without this branch the user is told the action failed while it sits in the approval queue, working perfectly.

Add these as the first statements of the catch where an error becomes the assistant's reply:

ts
import { approvalNotice } from '@veefore/sdk';

try {
  return await runAgentTurn(message);
} catch (error) {
  const notice = approvalNotice(error);
  if (notice) return notice.say;
  throw error;                      // real errors still behave as before
}

Three details that decide whether this works:

  • Use the name the block already binds. catch (err) means approvalNotice(err). If it binds nothing, change it to catch (error).
  • Reply the way the block already replies. If its other returns are objects, match them: return { summaryText: notice.say }, not a bare notice.say. A bare string where the caller expects an object is a type error that often will not show up until runtime.
  • Every catch that can receive one. A file can have several protected calls landing in different catch blocks thousands of lines apart. A branch in one of them leaves the others reporting failures. veefore doctor lists every uncovered block with its file and line.

Python:

python
from veefore import approval_notice

try:
    return await run_agent_turn(message)
except Exception as error:
    notice = approval_notice(error)
    if notice:
        return notice["say"]
    raise

Where that catch lives depends on your framework:

Your code Where the branch goes
try/catch around the turn That catch
Express, errors in app.use app.use((err, req, res, next) => …) — the four-argument one
Promise chain The .catch() whose value becomes the reply
Python try/except That except

#4b — collect the decision on a later turn

ts
import { resumeApprovals, withApprovalSession } from '@veefore/sdk';

async function onMessage(chatId: string, text: string) {
  // One session per conversation, so one user never finishes another
  // user's approved action.
  return withApprovalSession(chatId, async () => {
    // First, before the model runs: finish anything decided since last turn.
    for (const done of await resumeApprovals()) {
      history.push({ role: 'system', content: done.message });
    }
    return runModel([...history, { role: 'user', content: text }]);
  });
}

Python: resume_approvals(), or resume_approvals_async(), inside with_approval_session(chat_id).

What each protection does when the decision arrives:

Protection How it finishes
guard() The SDK holds your function and runs it. Done
Bridge call Veefore replays the approved request and makes the call. Done. Nothing local needs registering
checkpoint() Completes when the agent reaches the same checkpoint again. resumeApprovals() reports the decision but cannot run the block

That last row is why a checkpoint-protected action needs the user to ask again, or your code to re-enter that path, after approval. resumeApprovals() returns approved_retry for those so you can tell the user it is cleared to go.

#Step 5 — verify

bash
veefore integrate --check   # every registered tool reaches Veefore
veefore doctor              # the whole setup, including both defer halves
veefore dev                 # watch decisions arrive live

veefore doctor fails, rather than warns, on the mistakes that are invisible at runtime:

  • defer is set but nothing calls resumeApprovals()
  • a checkpoint() exists but no catch turns its raise into a reply
  • a catch that receives an approval has no branch, even though another one does
  • the installed SDK is too old to honour onApproval: 'defer' — it would ignore the option and wait, which is the hang you set defer to avoid

#How a chat agent behaves once this is in

Worth reading before you ship, because the sequence surprises people:

  1. The user asks for something that needs approval.
  2. The tool reaches Veefore. Policy says a human must decide.
  3. With defer, the call returns at once. Your catch branch (4a) replies with notice.say — something like "that needs a sign-off first".
  4. A reviewer approves in the console.
  5. On the next turn, resumeApprovals() (4b) finishes it and hands you a message to add to the history, so the agent can tell the user it is done.

For a guard()-protected tool or a bridge call, step 5 performs the action. For a checkpoint(), step 5 reports the decision and the action runs when that code path is reached again.

#Minimum versions

Feature SDK
guard(), approvals, waiting 1.0.0
checkpoint() 1.3.0
onApproval: 'defer', resumeApprovals(), approvalNotice() 1.6.0
onApproval on mcpCall / a2aInvoke 1.7.0

An older SDK does not reject a newer option — it ignores it. A project setting defer against SDK 1.5 waits instead, and the turn hangs. veefore doctor reports the mismatch.

Identity, authority, policy and enforcement for autonomous AI agents.