Knapsack

Running Agents (Knaps)

Execute Knapsack agents programmatically via API

Running Agents (Knaps)

Execute Knapsack agents programmatically by sending API requests to trigger agent workflows with custom tools and connections.

Overview

The Knap Runs API allows you to:

  • Execute pre-configured agents (Knaps) programmatically
  • Stream real-time agent execution results
  • Provide custom context and tools
  • Handle tool approvals and interactions
  • Monitor execution progress

Authentication

All requests require authentication using a Bearer token:

Authorization: Bearer YOUR_ACCESS_TOKEN

See Authentication for details on obtaining access tokens.

Execute Knap Run

Trigger a Knap run with a user message and available tools.

Endpoint

POST https://api.knapsack.ai/api/knap-tools/run

Request Body

{
  "messages": [
    {
      "role": "user",
      "content": "Summarize my emails from last week"
    }
  ],
  "tool_types": [
    {
      "tool_type": "gmail",
      "tool_description": "Access Gmail emails"
    },
    {
      "tool_type": "google_calendar",
      "tool_description": "Access Google Calendar events"
    }
  ],
  "stream_updates": true,
  "chat_id": "optional-existing-chat-id",
  "use_smart_cache": true,
  "knap_tool_uuid": "optional-knap-uuid"
}

Parameters

ParameterTypeRequiredDescription
messagesarrayYesArray of message objects with role and content
tool_typesarrayYesArray of available tools with type and description
stream_updatesbooleanNoEnable streaming responses (default: true)
chat_idstringNoExisting chat ID to continue conversation
use_smart_cachebooleanNoEnable smart caching (default: true)
knap_tool_uuidstringNoExecute a specific pre-configured Knap

Response (Streaming)

The API returns a Server-Sent Events (SSE) stream with various event types:

Event Types

1. Tool Execution Events

{
  "type": "tool_execution",
  "tool_name": "gmail",
  "status": "running",
  "queryUuid": "abc-123"
}

2. Tool Results

{
  "type": "tool_result",
  "tool_name": "gmail",
  "result": {
    "emails": [...]
  },
  "queryUuid": "abc-123"
}

3. Agent Messages

{
  "type": "message_chunk",
  "content": "I found 5 emails from last week...",
  "messageId": "msg-456"
}

4. Completion

{
  "type": "done",
  "messageId": "msg-456",
  "chatId": "chat-789"
}

5. Errors

{
  "type": "error",
  "error": "Error message",
  "details": {...}
}

Example Requests

curl -X POST https://api.knapsack.ai/api/knap-tools/run \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "What meetings do I have tomorrow?"
      }
    ],
    "tool_types": [
      {
        "tool_type": "google_calendar",
        "tool_description": "Access Google Calendar"
      }
    ],
    "stream_updates": true
  }'
const response = await fetch('https://api.knapsack.ai/api/knap-tools/run', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    messages: [
      {
        role: 'user',
        content: 'What meetings do I have tomorrow?'
      }
    ],
    tool_types: [
      {
        tool_type: 'google_calendar',
        tool_description: 'Access Google Calendar'
      }
    ],
    stream_updates: true
  })
});

// Handle streaming response
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value);
  const events = chunk.split('\n\n').filter(Boolean);

  for (const event of events) {
    if (event.startsWith('data: ')) {
      const data = JSON.parse(event.slice(6));
      console.log('Event:', data);

      if (data.type === 'done') {
        console.log('Execution complete');
      }
    }
  }
}
import requests
import json

url = "https://api.knapsack.ai/api/knap-tools/run"
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json"
}
payload = {
    "messages": [
        {
            "role": "user",
            "content": "What meetings do I have tomorrow?"
        }
    ],
    "tool_types": [
        {
            "tool_type": "google_calendar",
            "tool_description": "Access Google Calendar"
        }
    ],
    "stream_updates": True
}

with requests.post(url, headers=headers, json=payload, stream=True) as response:
    for line in response.iter_lines():
        if line:
            if line.startswith(b'data: '):
                data = json.loads(line[6:])
                print(f"Event: {data}")

                if data.get('type') == 'done':
                    print("Execution complete")
                    break
interface Message {
  role: string;
  content: string;
}

interface ToolType {
  tool_type: string;
  tool_description: string;
}

interface KnapRunRequest {
  messages: Message[];
  tool_types: ToolType[];
  stream_updates?: boolean;
  chat_id?: string;
  use_smart_cache?: boolean;
  knap_tool_uuid?: string;
}

async function executeKnapRun(
  accessToken: string,
  request: KnapRunRequest
): Promise<void> {
  const response = await fetch('https://api.knapsack.ai/api/knap-tools/run', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(request)
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.statusText}`);
  }

  const reader = response.body?.getReader();
  if (!reader) throw new Error('No response body');

  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const events = chunk.split('\n\n').filter(Boolean);

    for (const event of events) {
      if (event.startsWith('data: ')) {
        const data = JSON.parse(event.slice(6));

        switch (data.type) {
          case 'tool_execution':
            console.log(`Tool ${data.tool_name}: ${data.status}`);
            break;
          case 'message_chunk':
            console.log(`Agent: ${data.content}`);
            break;
          case 'done':
            console.log('Execution complete');
            return;
          case 'error':
            console.error('Error:', data.error);
            break;
        }
      }
    }
  }
}

// Usage
await executeKnapRun(accessToken, {
  messages: [
    {
      role: 'user',
      content: 'What meetings do I have tomorrow?'
    }
  ],
  tool_types: [
    {
      tool_type: 'google_calendar',
      tool_description: 'Access Google Calendar'
    }
  ],
  stream_updates: true
});

Execute Pre-configured Knap

Execute a specific pre-configured Knap by UUID:

{
  "messages": [
    {
      "role": "user",
      "content": "Run my weekly summary"
    }
  ],
  "tool_types": [],
  "knap_tool_uuid": "your-knap-uuid",
  "stream_updates": true
}

When executing a pre-configured Knap, the tool_types array can be empty as the Knap already has its tools configured.

Continuing Conversations

Continue an existing conversation by providing the chat_id:

{
  "messages": [
    {
      "role": "user",
      "content": "What about next week?"
    }
  ],
  "tool_types": [
    {
      "tool_type": "google_calendar",
      "tool_description": "Access Google Calendar"
    }
  ],
  "chat_id": "existing-chat-id",
  "stream_updates": true
}

Error Handling

Common Error Responses

401 Unauthorized

{
  "detail": "Not authenticated"
}

400 Bad Request

{
  "detail": "Invalid request parameters"
}

500 Internal Server Error

{
  "type": "error",
  "error": "Internal server error",
  "details": {...}
}

Handling Errors in Stream

Errors during execution are sent as SSE events:

for (const event of events) {
  if (event.startsWith('data: ')) {
    const data = JSON.parse(event.slice(6));

    if (data.type === 'error') {
      console.error('Execution error:', data.error);
      // Handle error appropriately
    }
  }
}

Rate Limits

API requests are subject to rate limiting. Current limits:

  • 100 requests per minute per user
  • 1000 requests per hour per organization

Best Practices

1. Handle Streaming Properly

Always handle the stream properly to avoid memory leaks:

const reader = response.body.getReader();
try {
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    // Process chunk
  }
} finally {
  reader.releaseLock();
}

2. Implement Timeout

Set appropriate timeouts for long-running operations:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 60000); // 60 second timeout

try {
  const response = await fetch(url, {
    signal: controller.signal,
    // ... other options
  });
} finally {
  clearTimeout(timeout);
}

3. Provide Descriptive Tool Descriptions

Help the agent understand available tools:

{
  "tool_types": [
    {
      "tool_type": "gmail",
      "tool_description": "Access Gmail emails, search, read, and compose"
    },
    {
      "tool_type": "google_calendar",
      "tool_description": "View and manage Google Calendar events"
    }
  ]
}

4. Use Smart Caching

Enable smart caching to improve performance:

{
  "use_smart_cache": true
}

Complete Example

Here's a complete example with proper error handling:

async function runKnapAgent(userMessage: string, tools: ToolType[]) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 60000);

  try {
    const response = await fetch('https://api.knapsack.ai/api/knap-tools/run', {
      method: 'POST',
      signal: controller.signal,
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        messages: [{ role: 'user', content: userMessage }],
        tool_types: tools,
        stream_updates: true,
        use_smart_cache: true
      })
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const reader = response.body?.getReader();
    if (!reader) throw new Error('No response body');

    const decoder = new TextDecoder();
    let buffer = '';

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const events = buffer.split('\n\n');
        buffer = events.pop() || '';

        for (const event of events) {
          if (event.startsWith('data: ')) {
            try {
              const data = JSON.parse(event.slice(6));
              handleEvent(data);

              if (data.type === 'done') {
                return data;
              }
            } catch (e) {
              console.error('Error parsing event:', e);
            }
          }
        }
      }
    } finally {
      reader.releaseLock();
    }
  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('Request timed out');
    } else {
      console.error('Error executing Knap run:', error);
    }
    throw error;
  } finally {
    clearTimeout(timeout);
  }
}

function handleEvent(data: any) {
  switch (data.type) {
    case 'tool_execution':
      console.log(`🔧 ${data.tool_name}: ${data.status}`);
      break;
    case 'tool_result':
      console.log(`✅ ${data.tool_name} completed`);
      break;
    case 'message_chunk':
      process.stdout.write(data.content);
      break;
    case 'done':
      console.log('\n\n✨ Execution complete');
      break;
    case 'error':
      console.error(`❌ Error: ${data.error}`);
      break;
  }
}

Next Steps