Skip to content

flue-guardGovernance layer for Flue tools

Per-call authorization, idempotency, and a tamper-evident audit trail for agent tools, in-process.

Thirty seconds โ€‹

Flue's own guidance: "a tool's parameters are model-selected inputs, not an authorization boundary." flue-guard is that boundary. The model controls the arguments; your application controls the context. Every call is decided by comparing the two, then written to a hash-chained receipt.

ts
import * as v from "valibot";
import { govern, caller } from "flue-guard";

declare const accounts: {
  ownedBy(accountId: string, actorId: string): Promise<boolean>;
  sendResetLink(accountId: string): Promise<void>;
};

const gov = govern({ audit: "audit.jsonl" }); // hash-chained JSONL receipt

export const resetPassword = gov.tool({
  name: "reset_password",
  description: "Send a password reset link.",
  parameters: v.object({ accountId: v.string() }),
  sideEffect: true,
  // The check that was missing in the Meta incident:
  authorize: caller(
    (a: { accountId: string }, ctx) => accounts.ownedBy(a.accountId, ctx.actor.id),
  ),
  idempotency: { key: (a) => `reset:${a.accountId}` }, // a retry won't send twice
  execute: async (a) => {
    await accounts.sendResetLink(a.accountId);
    return "Sent.";
  },
});

For dispatched Flue 2 agents, call gov.withContext(trustedContext) inside the agent function, then mount bound.tool(...) with useTool. Derive the caller from authenticated signal attributes returned by useDelivery(). An ambient gov.run(...) around dispatch does not reach the detached agent execution. See the dispatched-agent example.

Start with the tutorial: from npm i to a denied call and a verified audit line in under five minutes.