Custom tool policy
Source: node/custom-tool-policy
What this example demonstrates
A minimal TypeScript example that uses Agent Assembly governance directly, with no
agent framework. It governs two raw TypeScript tools with withAssembly() and a
custom GatewayClient that enforces a local policy:
read_fileis allowed — it executes and returns mock file contents.write_fileis denied — it is blocked at the policy layer before the tool body runs.
The governance point: you do not need an agent framework to get policy enforcement.
withAssembly wraps any object of { execute } tools and checks each call against
the policy first.
The framework / library
None. The example depends only on @agent-assembly/sdk (version 0.0.1-alpha.9.1
in its package.json). It runs fully offline — no gateway and no @langchain/core.
How it works
src/policy.ts defines two rules (read_file → allow, write_file → deny) and
builds an in-process GatewayClient whose check returns { denied: true, reason }
for denied tools. src/index.ts passes that client to withAssembly along with the
two tool definitions. When tools.write_file.execute(...) is called, withAssembly
runs the policy check, sees a deny, and throws a PolicyViolationError — the
write_file body never runs.
Prerequisites & running it
See Preparing the runtime environment for the shared prerequisites. Then:
cd node/custom-tool-policy
pnpm install
pnpm start
This example is mock/offline only — no provider keys or gateway URL are needed. To
connect to a real gateway, set AAASM_GATEWAY_URL in your environment directly. Run
pnpm test for the offline smoke test and pnpm typecheck for the type check.
Code walkthrough
The policy is a simple rule table, evaluated by tool name:
export const POLICY_RULES: PolicyRule[] = [
{ tool: "read_file", action: "allow", reason: "Read-only file access is safe to execute." },
{ tool: "write_file", action: "deny", reason: "Write operations to the filesystem require explicit approval." },
];
The local-policy GatewayClient turns a deny rule into a denied check result:
check: async (request) => {
const rule = evaluate(request.toolName ?? "");
return rule.action === "deny"
? { denied: true, reason: rule.reason }
: { denied: false };
},
index.ts wraps the tools and handles the denied call by catching
PolicyViolationError:
const tools = withAssembly(
{
read_file: { execute: async (args) => readFile(String(args.path ?? "")).output },
write_file: { execute: async (args) => writeFile(String(args.path ?? ""), String(args.content ?? "")).output },
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "custom-tool-policy-agent" }
);
try {
await tools.write_file.execute({ path: "/etc/config", content: "override settings" });
} catch (err) {
if (err instanceof PolicyViolationError) {
console.log(` [BLOCKED] ${err.message}`);
}
}
Notes & caveats
:::note Offline by design The example uses only mock/offline mode. No provider keys or gateway URL are needed, and all tests run offline. :::
:::tip Default-deny
The policy's evaluate() returns a deny rule for any unlisted tool — unknown tools
are denied by default.
:::
Expected behavior
=== Custom Tool Policy — Minimal TypeScript Example ===
No agent framework required. Using @agent-assembly/sdk directly.
Calling allowed tool: read_file
[ALLOW] Contents of "/data/report.txt": [mock file contents line 1, line 2]
Calling denied tool: write_file
[BLOCKED] Tool 'write_file' blocked: Write operations to the filesystem require explicit approval.
All tool calls governed by withAssembly + the local policy.
Links
- Example directory:
node/custom-tool-policy - Example README:
README.md