Skip to content

Require approval for risky agent calls

The job: your agent can call real tools, and you want a person to say yes before the dangerous ones run.

Since Matimo 0.2.0 this is on by default. HTTP DELETE tools and command (shell) tools ask on every call. A tool can also opt in with requires_approval: true in its YAML.

A call that needs approval and has nobody to ask fails closed. You supply the person (or the UI) as a callback.

import { MatimoInstance, JsonlFileSink, verifyAuditLog, type ApprovalRequest } from '@matimo/core';
const matimo = await MatimoInstance.init({
toolPaths: ['./tools'],
onApproval: async (request: ApprovalRequest) => {
// request.toolName, request.description, request.params
return await askReviewer(request); // true = run it, false = refuse
},
auditSink: new JsonlFileSink('./audit.jsonl'),
});
from matimo import ApprovalRequest, Matimo, JsonlFileSink, verify_audit_log
async def on_approval(request: ApprovalRequest) -> bool:
# request.tool_name, request.description, request.params
return await ask_reviewer(request)
matimo = await Matimo.init(
"./tools",
on_approval=on_approval,
audit_sink=JsonlFileSink("./audit.jsonl"),
)

If the callback returns false, the call is refused and the agent gets an error.

Every call ends in an audit event. With JsonlFileSink each line is linked to the one before it, and verification reports the first line that was edited.

verifyAuditLog('./audit.jsonl'); // { valid: true, entries: 3 }
verify_audit_log("./audit.jsonl") # AuditLogVerification(valid=True, entries=3)

The home page demo replays a real run of this flow.

  • The log is tamper evident, not tamper proof. Verification shows an edit or a deleted line. It does not prevent one.
  • You write the reviewer. Matimo asks; it does not decide who your reviewers are.
  • With no onApproval, a call that needs approval fails. That is the intended behavior.
  • MATIMO_AUTO_APPROVE=true approves everything unseen. Use it only in throwaway test environments.