Permission modes

The current SDKs all expose a mode that controls whether tools run immediately or stop for review.

  • `interactive`: emit permission requests and wait for your app to respond.
  • `unrestricted`: allow all tool execution without asking.
  • `restricted`: block risky operations automatically.
  • `external`: hand the decision to your own callback or policy layer where supported.

TypeScript

TypeScript

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

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

  await sdk.start();

  for await (const event of sdk.streamPrompt({
    message: 'Run the test suite and summarize failures.'
  })) {
    if (event.type === 'permission_request') {
      console.log('Permission needed for', event.tool);
      await sdk.permissionResponse({
        requestId: event.requestId,
        allowed: event.tool !== 'run_command',
      });
      continue;
    }

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

  await sdk.stop();
}

main();

Python

import asyncio
from autohand_sdk import AutohandSDK

async def main():
    async with AutohandSDK(cwd='.', permission_mode='interactive') as sdk:
        async for event in sdk.stream_prompt('Run the test suite and summarize failures.'):
            if event['type'] == 'permission_request':
                await sdk.respond_to_permission(
                    event['request_id'],
                    decision='allow',
                    allowed=event.get('tool') != 'run_command',
                )
                continue

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

asyncio.run(main())

Go

package main

import (
    "context"
    "fmt"
    "log"

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

func main() {
    ctx := context.Background()
    sdk := autohand.NewSDK(&autohand.Config{
        CWD: ".",
        PermissionMode: autohand.PermissionInteractive,
    })

    if err := sdk.Start(ctx); err != nil {
        log.Fatal(err)
    }
    defer sdk.Close()

    events, err := sdk.StreamPrompt(ctx, &autohand.PromptParams{
        Message: "Run the test suite and summarize failures.",
    })
    if err != nil {
        log.Fatal(err)
    }

    for event := range events {
        switch e := event.(type) {
        case autohand.PermissionRequestEvent:
            if err := sdk.PermissionResponse(ctx, e.RequestID, true, autohand.ScopeOnce); err != nil {
                log.Fatal(err)
            }
        case autohand.MessageUpdateEvent:
            fmt.Print(e.Delta)
        }
    }
}

Swift

import AgentSDK
import Foundation

let hookManager = HookManager()
let permissionManager = PermissionManager(
    hookManager: hookManager,
    mode: .ask
)
Runner.setPermissionManager(permissionManager)

let provider = OpenAIProvider(apiKey: "sk-...")
let agent = Agent(
    name: "Reviewer",
    instructions: "Review commands before anything destructive runs.",
    tools: [.readFile, .bash],
    model: ModelID("gpt-4o"),
    provider: provider
)

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

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

Java

import ai.autohand.sdk.sdk.AutohandSDK;
import ai.autohand.sdk.types.DecisionScope;
import ai.autohand.sdk.types.Events;
import ai.autohand.sdk.types.PromptParams;
import ai.autohand.sdk.types.SDKConfig;

AutohandSDK sdk = new AutohandSDK(new SDKConfig(
    ".",
    null,
    false,
    300000
));

sdk.start();
sdk.streamPrompt(new PromptParams("Run the test suite and summarize failures."), event -> {
    if (event instanceof Events.PermissionRequestEvent pre) {
        sdk.allowPermission(pre.requestId(), DecisionScope.ONCE);
    } else if (event instanceof Events.MessageUpdateEvent mue) {
        System.out.print(mue.delta());
    }
});
sdk.stop();

Approval patterns

Most apps end up using one of these shapes:

  • Auto-allow read-only tools and ask on shell or write operations.
  • Allow everything inside a short-lived sandboxed worker.
  • Deny dangerous tools and return a manual follow-up task to a human.
  • Remember a decision for a single session while a review flow stays open.

Selective approval

TypeScript

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

for await (const event of sdk.streamPrompt({ message: 'Inspect git status and run tests.' })) {
  if (event.type !== 'permission_request') {
    continue;
  }

  const autoAllow = event.tool === 'read_file' || event.tool === 'git_status';
  await sdk.permissionResponse({
    requestId: event.requestId,
    allowed: autoAllow,
    remember: autoAllow,
  });
}

Python

async for event in sdk.stream_prompt('Inspect git status and run tests.'):
    if event['type'] != 'permission_request':
        continue

    auto_allow = event.get('tool') in ('read_file', 'git_status')
    await sdk.respond_to_permission(
        event['request_id'],
        decision='allow' if auto_allow else 'deny',
        allowed=auto_allow,
    )

Go

for event := range events {
    req, ok := event.(autohand.PermissionRequestEvent)
    if !ok {
        continue
    }

    autoAllow := req.Tool == "read_file" || req.Tool == "git_status"
    if err := sdk.PermissionResponse(ctx, req.RequestID, autoAllow, autohand.ScopeOnce); err != nil {
        log.Fatal(err)
    }
}

Best practices

  • Make your approval UI show the tool name, description, and working directory.
  • Do not silently auto-allow shell commands unless the agent is inside an explicit sandbox boundary.
  • Keep the permission mode close to the job. Read-only review jobs and write-enabled refactor jobs should not share the same default.
  • Log every allow or deny decision if the run matters for CI, auditing, or customer support.