Skip to content
Français

Control tool permissions

Allow or deny tool calls and use hooks to inspect the built-in agent loop.

Use permissions to decide which tool calls are allowed, and hooks to run your code at specific points in the built-in loop. Both are options of createHarness(). For commands that prepare a sandbox before a turn, use environment hooks instead.

The model can read any file except .env files, edit under src/ and test/, and run npm test. Any other call returns Denied: <reason> to the model as a failed tool result, and the loop continues. Pass harness to createAgent({ harness, model }).

export const rules = [
  {
    effect: "deny",
    paths: ["**/.env*"],
    reason: "Environment files are private.",
  },
  { effect: "allow", tools: ["read_file", "list_files"] },
  {
    effect: "allow",
    tools: ["write_file", "edit_file"],
    paths: ["src/**", "test/**"],
  },
  { effect: "allow", tools: ["shell"], commands: ["npm test"] },
] as const;
import { defineHarnessPermissions } from "@elie-laloum/outpost";
import { rules } from "./permission-rules.ts";

export const permissions = defineHarnessPermissions({ default: "deny", rules });
import { createAnthropicModelProvider } from "@elie-laloum/outpost";

export const modelProvider = createAnthropicModelProvider({
  apiKey: process.env.ANTHROPIC_API_KEY ?? "",
});
import {
  createHarness,
  createHarnessFileTools,
  createHarnessEditTools,
  createHarnessShellTools,
} from "@elie-laloum/outpost";
import { modelProvider } from "./permission-model.ts";
import { permissions } from "./permissions.ts";

export const harness = createHarness({
  modelProvider,
  tools: [
    createHarnessFileTools(),
    createHarnessEditTools(),
    createHarnessShellTools(),
  ],
  permissions,
});

Each rule has an effect ("allow" or "deny") and one or more conditions. A rule matches when all its conditions match.

API reference: HarnessPermissionRule.

The first matching rule decides. Without a match, default applies; it is "allow" when omitted. An allow rule with paths needs every path of the call to match; a deny rule needs only one.

paths and commands match only tools that declare them. The built-in file, edit and search tools declare their paths; shell and git declare their command. For your own tools, declare resources(input) (see Tools).

Use hooks to add instructions at the start, refuse write_file calls after step 20 and ask for a test report before the agent finishes. Pass the exported list to createHarness({ hooks }).

import { defineHarnessHook } from "@elie-laloum/outpost";

export const startHook = defineHarnessHook({
  on: "session-start",
  run: () => ({ instructions: "Run npm test before you answer." }),
});
export const editHook = defineHarnessHook({
  on: "before-tool",
  run({ call, step }) {
    if (call.name === "write_file" && step > 20)
      return { deny: "Stop editing and summarize your changes." };
  },
});
import { defineHarnessHook } from "@elie-laloum/outpost";

export const completionHook = defineHarnessHook({
  on: "stop",
  run({ text }) {
    if (!text.includes("npm test"))
      return { continue: "Run npm test and report its result." };
  },
});
import { startHook, editHook } from "./editing-hooks.ts";
import { completionHook } from "./completion-hook.ts";

export const hooks = [startHook, editHook, completionHook];

Pass the list to createHarness({ hooks }). A hook that returns nothing leaves the loop unchanged.

API reference: HarnessHookPhase, HarnessHookEvents, HarnessHookDecisions and HarnessHookContext.

Drag to move · Ctrl + scroll to zoom
100 %
  • CheckBefore the tool runs.
    1. ValidateThe input must match the tool’s schema.
    2. Evaluate permissionsA denial ends the call; hooks do not run. permissions
    3. Run before-tool hooksIn declaration order. A deny ends the chain; an input goes to the next hook. before-tool
    4. Re-check a rewriteOutpost validates the new input and evaluates permissions again. permissions
    (Steps)
    • → Run : then
  • RunThe tool executes against the sandbox. (Steps)
    • → Return : then
  • ReturnThe model receives the result.
    1. Run after-tool hooksAlso for denied and failed calls. Each can replace the result. after-tool
    (Steps)

Hooks of the same phase run in declaration order. For stop, the first hook that returns { continue } wins, and the extra step still counts toward limits.maxSteps.

A subagent call runs only if the child’s permissions and those of every ancestor allow it, including after a hook rewrite. Hooks stay with the harness that declares them: parent hooks do not see the child’s calls.

  • Rules see only what a tool declares in resources(input). A tool without it can be matched by name only.
  • commands: ["npm test*"] also allows npm test; rm -rf src. List exact command lines.
  • Absolute paths and paths leaving the repository match no paths pattern, so path deny rules do not catch them. The built-in tools refuse such paths; check them in your own tools.
  • Rules and hooks control which calls start, not what an allowed tool does: npm test runs whatever the test script runs. Isolation comes from the sandbox; see Security.

API: defineHarnessPermissions · HarnessPermissionRule · defineHarnessHook · HarnessHookPhase · createHarness.