Permission modes

Set the mode when the SDK starts. The mode applies to every tool call until you change it.

  • interactive (default): emit `permission_request` events and wait for your code to allow or deny.
  • unrestricted: allow every tool call without prompting. Use only inside a sandbox.
  • restricted: deny risky tools automatically. Reads still pass through.
  • external: hand the decision to a configured callback or hook.

Legacy aliases like `default` and `bypassPermissions` still work, but new code should use the names above.

Set the mode at session start

Pass the mode into the SDK constructor. The default is `interactive`, which is the safest choice for any agent that runs untrusted prompts.

TypeScript

import { AutohandSDK } from '@autohandai/agent-sdk';

const sdk = new AutohandSDK({
  cwd: '.',
  permissionMode: 'interactive',
});

await sdk.start();

Python

from autohand_sdk import AutohandSDK

async with AutohandSDK(
    cwd=".",
    permission_mode="interactive",
) as sdk:
    ...

Go

package main

import (
    "context"
    autohand "github.com/autohandai/agent-sdk-go"
)

ctx := context.Background()
sdk := autohand.NewSDK(&autohand.Config{
    CWD: ".",
    PermissionMode: autohand.PermissionInteractive,
})
_ = sdk.Start(ctx)
defer sdk.Close()

Swift

import AgentSDK

let manager = PermissionManager(
    hookManager: HookManager(),
    mode: .interactive
)
Runner.setPermissionManager(manager)

Java

import ai.autohand.sdk.sdk.AutohandSDK;
import ai.autohand.sdk.types.SDKConfig;
import ai.autohand.sdk.types.PermissionMode;

AutohandSDK sdk = new AutohandSDK(new SDKConfig(
    ".", null, false, 300_000, PermissionMode.INTERACTIVE
));
sdk.start();

Respond to permission requests

In `interactive` mode the SDK emits a `permission_request` event whenever the agent reaches a guarded tool. Your code decides what to do, then calls back with `allowed` and an optional `remember` scope.

TypeScript

for await (const event of sdk.streamPrompt({
  message: 'Run the test suite and report failures.'
})) {
  if (event.type === 'permission_request') {
    const allowReads = event.tool === 'read_file' || event.tool === 'git_status';
    await sdk.permissionResponse({
      requestId: event.requestId,
      allowed: allowReads,
      remember: allowReads,
    });
    continue;
  }

  if (event.type === 'message_update') {
    process.stdout.write(event.delta);
  }
}

Python

async for event in sdk.stream_prompt("Run the test suite and report failures."):
    if event["type"] == "permission_request":
        tool = event.get("tool", "")
        allow_reads = tool in ("read_file", "git_status")
        await sdk.respond_to_permission(
            event["request_id"],
            decision="allow" if allow_reads else "deny",
            allowed=allow_reads,
            remember=allow_reads,
        )
        continue

    if event["type"] == "message_update":
        print(event.get("delta", ""), end="")

Go

events, _ := sdk.StreamPrompt(ctx, &autohand.PromptParams{
    Message: "Run the test suite and report failures.",
})

for event := range events {
    switch e := event.(type) {
    case autohand.PermissionRequestEvent:
        allow := e.Tool == "read_file" || e.Tool == "git_status"
        scope := autohand.ScopeOnce
        if allow {
            scope = autohand.ScopeSession
        }
        _ = sdk.PermissionResponse(ctx, e.RequestID, allow, scope)
    case autohand.MessageUpdateEvent:
        fmt.Print(e.Delta)
    }
}

Swift

let stream = Runner.runStream(
    agent: agent,
    prompt: "Run the test suite and report failures."
)

for try await event in stream {
    if event.type == .content, let data = event.data {
        print(data, terminator: "")
    }
}

Java

sdk.streamPrompt(
    new PromptParams("Run the test suite and report failures."),
    event -> {
        if (event instanceof Events.PermissionRequestEvent pre) {
            boolean allowReads = pre.tool().equals("read_file");
            if (allowReads) {
                sdk.allowPermission(pre.requestId(), DecisionScope.SESSION);
            } else {
                sdk.denyPermission(pre.requestId(), DecisionScope.ONCE);
            }
        } else if (event instanceof Events.MessageUpdateEvent mue) {
            System.out.print(mue.delta());
        }
    }
);

Use the ergonomic helpers when they read better than `permissionResponse`:

TypeScript

await sdk.allowPermission(event.requestId, 'session');
await sdk.denyPermission(event.requestId, 'once');
await sdk.suggestPermissionAlternative(
  event.requestId,
  'Run bun run typecheck before bun run build.'
);

Python

await sdk.allow_permission(event["request_id"], scope="session")
await sdk.deny_permission(event["request_id"], scope="once")
await sdk.suggest_permission_alternative(
    event["request_id"],
    "Run bun run typecheck before bun run build.",
)

Allow and deny lists

Use `PermissionSettings` to lock down which tools can run, by name or by pattern. Allow lists pass through, deny lists block.

TypeScript

import { AutohandSDK } from '@autohandai/agent-sdk';

const sdk = new AutohandSDK({
  cwd: '.',
  permissions: {
    mode: 'interactive',
    allowList: ['read_file', 'write_file', 'git_status'],
    denyList: ['delete_path', 'run_command'],
    allowPatterns: ['git *', 'npm install'],
    denyPatterns: ['rm -rf', 'sudo'],
  },
});

Python

from autohand_sdk import AutohandSDK, PermissionSettings

sdk = AutohandSDK(
    cwd=".",
    permissions=PermissionSettings(
        mode="interactive",
        allow_list=["read_file", "write_file", "git_status"],
        deny_list=["delete_path", "run_command"],
    ),
)

Yolo patterns for unattended runs

For headless or batch jobs, set a yolo pattern that auto-approves whitelisted tools and expires after a timeout.

TypeScript

const sdk = new AutohandSDK({
  cwd: '.',
  yolo: 'allow:read,write',
  yoloTimeout: 60, // seconds
});

Python

sdk = AutohandSDK(
    cwd=".",
    yolo="allow:read,write",
    yolo_timeout=60,
)

Change the mode at runtime

The TypeScript SDK exposes a runtime override that calls `autohand.permissionModeSet` over JSON-RPC. Other SDKs require restarting the session for the new mode to take effect.

TypeScript

await sdk.setPermissionMode('unrestricted');
// later
await sdk.setPermissionMode('interactive');

Best practices

  • Default to `interactive` mode in any agent that accepts untrusted prompts.
  • Show the tool name, description, and working directory in your approval UI so reviewers can decide quickly.
  • Auto-allow read-only tools and ask before shell or write operations unless the run is inside a sandbox.
  • Pair `unrestricted` mode with a `yoloTimeout` so a forgotten run cannot keep approving forever.
  • Log every allow or deny decision when the run is part of CI, audit, or customer support flows.