# Exec

**Page:** kit/exec

[Download Raw Markdown](./kit/exec.md)

---

**The most powerful abstraction in computing: Your code IS the API.**

Write a TypeScript/JavaScript function in a file. That's it. You now have an HTTP endpoint—complete with automatic routing, JSON serialization, dependency installation, and production-grade execution. No Express. No Fastify. No webserver configuration. No package.json. **Just write code in files, and they become URLs.**

Create `scripts/1/api/users/[id].ts` → Get instant endpoint at `https://your-container-exec-1.SERVER.containers.hoody.icu/api/users/123`. The `1` is your instance directory (storage), not part of the URL path. Want WebSockets? Add two magic comments. Need shared state? It's already there. Dependencies? Auto-installed. This is what happens when you **design the web server around simplicity, not complexity.**


Traditional: Install Express, configure routes, manage middleware, setup CORS, handle errors, deploy to server, configure load balancer...

Hoody Exec: Write function in file. **Done.** It's live. It's an API. Other services can call it. AI can discover it. Users can hit it. That's the abstraction.
</Aside>


Hoody Exec uses **V8 isolates** for secure script execution and is production-ready. However, it's designed for running **YOUR code or trusted code** - not arbitrary untrusted code from random users. Perfect for your own APIs, internal tools, and trusted applications.
</Aside>

## Two Execution Modes - THE CORE CONCEPT

Everything in Hoody Exec revolves around choosing the right execution mode. This one decision changes everything about how your code runs.

### Worker Mode - Persistent & Stateful

```javascript
// @mode worker
```

**Think**: Long-running Node.js server that never restarts

- ✅ **Persistent VM** - Created once, runs forever
- ✅ **Shared State** - `shared` object persists across all requests
- ✅ **WebSocket Support** - Required for real-time connections
- ✅ **No Concurrency Limits** - Handle unlimited simultaneous requests
- ✅ **Pre/Post Middleware** - `pre.js` and `post.js` run for every request
- ✅ **Zero Cold Start** - VM is already warm
- ❌ State lost on container restart (use SQLite for persistence)
- ❌ Higher memory usage (persistent VM overhead)

**Perfect For**:
- WebSocket servers (chat, live dashboards, real-time updates)
- Session management and caching
- High-traffic APIs (no cold starts)
- Rate limiting with per-IP tracking
- **Hoody AI interception** (MITM proxy for controlling AI requests)
- Any scenario where you need state that survives across requests

### Serverless Mode - Isolated & Fresh

```javascript
// @mode serverless  // or omit (default)
```

**Think**: AWS Lambda, Vercel Functions, Cloudflare Workers

- ✅ **Fresh Context** - Brand new VM for every request
- ✅ **Complete Isolation** - Zero state leakage between requests
- ✅ **Concurrency Control** - `@concurrent 5` limits parallel execution
- ✅ **Lower Memory** - No persistent VM overhead
- ✅ **Safer for Untrusted Code** - Better isolation guarantees
- ❌ No WebSocket support (no persistent connections)
- ❌ No shared state across requests
- ❌ Slight overhead per request (VM creation)

**Perfect For**:
- Webhook receivers (Stripe, GitHub, Slack)
- Isolated tasks (data processing, API calls)
- Sporadic traffic (pay-per-execution model)
- Stateless microservices
- Untrusted user scripts (better isolation)
- Any scenario where isolation matters more than performance

### Mode Comparison at a Glance

| Feature | Worker Mode | Serverless Mode |
|---------|------------|-----------------|
| **State** | Persistent `shared` object | None (fresh each time) |
| **WebSocket** | ✅ Supported | ❌ Not available |
| **Concurrency** | Unlimited (handle in code) | Configurable via `@concurrent` |
| **Startup** | Once (fast subsequent requests) | Per request (slight overhead) |
| **Memory** | Higher (persistent VM) | Lower (ephemeral) |
| **Use Case** | Real-time, stateful APIs | Webhooks, isolated tasks |

## What You Can Do

- 🎯 **Two Modes** - Worker (persistent) or Serverless (isolated)
- 🚀 **File = API** - Create file → instant HTTP endpoint (no server config)
- ✨ **Magic Comments** - Configure with `// @mode worker`, `// @cors *`, `// @timeout 5000`
- 🗺️ **File-Based Routing** - `api/users/[id].ts` → `/api/users/123` with dynamic parameters
- ⚡ **Powered by Bun** - Modern JavaScript runtime (faster than Node.js)
- 🤖 **AI Code Generation** - Write `// @ai-generate REST API` → LLM creates implementation
- 📦 **Auto-Install Dependencies** - Use `require('axios')` → automatically installed (no package.json)
- 🎨 **Templates** - Scaffold from built-in or custom templates
- 🔍 **Code Validation** - TypeScript checking, syntax validation, dependency analysis
- 💾 **Shared State** - In-memory KV store across requests (worker mode only)
- 🔌 **WebSocket Support** - Real-time bidirectional communication (worker mode only)
- 📊 **Monitoring** - Performance metrics, log streaming, cache stats

## Automatically Available Variables

**Every script automatically receives these parameters - no imports, no configuration needed:**

<div style="margin: 1.5rem 0; padding: 1.25rem; background: var(--sl-color-bg-sidebar); border: 2px solid var(--sl-color-hairline); border-radius: 8px;">

### Core HTTP Objects

```typescript
// ✅ req - Incoming HTTP request
req.url          // '/api/users/123'
req.method       // 'GET', 'POST', etc.
req.headers      // { 'authorization': 'Bearer ...', ... }
req.body         // Parsed JSON body (if Content-Type: application/json)

// ✅ res - HTTP response (for full control)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ success: true }))
res.statusCode = 404  // Set status code
res.setHeader('X-Custom', 'value')
```

### Metadata Object

```typescript
// ✅ metadata - Request context and routing info
metadata.executionId      // Unique execution ID
metadata.parameters       // { id: '123' } from dynamic routes like [id].ts
metadata.clientIp         // ⚡ REAL client IP (see below)
metadata.path             // '/api/users/123'
metadata.method           // 'GET', 'POST', etc.
metadata.url              // Full URL
metadata.query            // Parsed query string { search: 'term' }
```

### State & Tools

```typescript
// ✅ shared - State object (persists in worker mode, resets in serverless)
shared.cache = new Map()       // Worker: persists across requests
shared.requestCount = 0        // Serverless: reset every request
shared.sessions = new Set()    // Lost on container restart/reboot (both modes)

// ✅ console - Logger
console.log('message')
console.info('info')
console.debug('debug')
console.error('error')

// ✅ require - Module loader (auto-installs missing modules)
const axios = require('axios')      // Auto-installed if not present
const crypto = require('crypto')    // Built-in modules work too

// ✅ ws - WebSocket context (if @websocket enabled)
ws.message = (socket, data) => { ... }
ws.broadcast(data)
ws.connections.size

// ✅ mainResult - (post.js ONLY) Result from main script
// Available in post.js middleware for wrapping responses
```

</div>



**`metadata.clientIp` contains the REAL client IP address** - not the proxy IP.

Unlike traditional reverse proxies where you must parse `X-Forwarded-For` headers, Hoody preserves the actual client IP at the infrastructure level using custom netfilter hooks. Your code sees the real IP directly:

```javascript
// ✅ CORRECT - Just use it directly
const clientIp = metadata.clientIp;  // 203.0.113.50 (real user IP)

// ❌ WRONG - Don't parse headers (not needed, won't work)
const clientIp = req.headers['x-forwarded-for'];  // undefined
```

**This works for rate limiting, geolocation, access control, analytics - everything.**

See [Hoody Proxy - Real Client IPs](/foundation/proxy/#real-client-ips-zero-configuration) for technical details on how this infrastructure-level IP preservation works.

</Aside>

## Complete Examples

Real-world patterns showing Worker vs Serverless modes with full request/response handling.


  

**Worker Mode** (with shared state caching):
```javascript
// api/users/[id].ts
// @mode worker
// @cors reflective
// @timeout 5000
// @log-level standard

// Initialize cache on first request
if (!shared.usersCache) {
  shared.usersCache = new Map();
  shared.cacheHits = 0;
  shared.cacheMisses = 0;
}

const userId = metadata.parameters.id;

// Check cache first
if (shared.usersCache.has(userId)) {
  shared.cacheHits++;
  return {
    user: shared.usersCache.get(userId),
    cached: true,
    cacheHitRate: shared.cacheHits / (shared.cacheHits + shared.cacheMisses)
  };
}

// Fetch from database (cache miss)
shared.cacheMisses++;
const user = await fetchUserFromDatabase(userId);

// Cache for next request
shared.usersCache.set(userId, user);

return {
  user,
  cached: false,
  cacheHitRate: shared.cacheHits / (shared.cacheHits + shared.cacheMisses)
};
```

**Serverless Mode** (fresh, stateless):
```javascript
// api/users/[id].ts
// @mode serverless
// @concurrent 10
// @cors reflective
// @timeout 5000
// @log-level standard

const userId = metadata.parameters.id;

// Validate input
if (!userId || userId.length !== 24) {
  res.statusCode = 400;
  return {
    error: 'Invalid user ID format',
    expected: '24-character hex string'
  };
}

// Fresh database query every time (no cache)
const user = await fetchUserFromDatabase(userId);

if (!user) {
  res.statusCode = 404;
  return {
    error: 'User not found',
    userId
  };
}

// Clean response (isolated execution)
return {
  user,
  requestId: metadata.executionId
};
```

  </TabItem>
  

**Worker Mode Only** (WebSocket requires persistent VM):
```javascript
// chat/rooms/[roomId].ts
// @mode worker
// @websocket
// @cors reflective
// @timeout 0

const roomId = metadata.parameters.roomId;

// Initialize room state
if (!shared.rooms) {
  shared.rooms = new Map();
}

if (!shared.rooms.has(roomId)) {
  shared.rooms.set(roomId, {
    users: new Map(),
    messages: [],
    created: new Date()
  });
}

const room = shared.rooms.get(roomId);

// Serve HTML UI for HTTP requests
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
  <!DOCTYPE html>
  <html>
    <head><title>Chat Room: ${roomId}</title></head>
    <body>
      <div id="messages"></div>
      <input id="input" placeholder="Type message..." />
      <script>
        const ws = new WebSocket(location.href.replace('http', 'ws'));
        ws.onmessage = e => {
          const div = document.createElement('div');
          div.textContent = e.data;
          document.getElementById('messages').appendChild(div);
        };
        document.getElementById('input').onkeypress = e => {
          if (e.key === 'Enter') {
            ws.send(e.target.value);
            e.target.value = '';
          }
        };
      </script>
    </body>
  </html>
`);

// WebSocket handlers
ws.open = (socket, req) => {
  const userId = socket.data.executionId;
  room.users.set(userId, {
    connectedAt: new Date(),
    ip: socket.data.ip
  });
  
  // Broadcast join message to all in room
  ws.broadcast(JSON.stringify({
    type: 'join',
    roomId,
    userId,
    userCount: room.users.size
  }));
};

ws.message = (socket, data) => {
  const message = {
    type: 'message',
    roomId,
    userId: socket.data.executionId,
    text: data,
    timestamp: new Date().toISOString()
  };
  
  // Save to room history
  room.messages.push(message);
  
  // Broadcast to all users in THIS room only
  ws.broadcast(JSON.stringify(message));
};

ws.close = (socket, code, reason) => {
  const userId = socket.data.executionId;
  room.users.delete(userId);
  
  // Cleanup empty rooms
  if (room.users.size === 0 && room.messages.length === 0) {
    shared.rooms.delete(roomId);
  }
  
  ws.broadcast(JSON.stringify({
    type: 'leave',
    roomId,
    userId,
    userCount: room.users.size
  }));
};
```

  </TabItem>
  

**Serverless Mode Best Practice** (isolation prevents cross-webhook contamination):
```javascript
// webhooks/stripe.ts
// @mode serverless
// @concurrent false  // Process webhooks serially
// @cors none
// @timeout 30000
// @log-level full
// @log-request-body true

// Validate webhook signature
const signature = req.headers['stripe-signature'];
if (!signature) {
  res.statusCode = 401;
  return {
    error: 'Missing signature',
    message: 'Stripe-Signature header required'
  };
}

// Parse webhook payload
let event;
try {
  const rawBody = await getRawBody(req);
  event = JSON.parse(rawBody);
} catch (err) {
  res.statusCode = 400;
  return {
    error: 'Invalid payload',
    message: err.message
  };
}

// Verify signature (use crypto)
const crypto = require('crypto');
const secret = process.env.STRIPE_WEBHOOK_SECRET;
const expectedSig = crypto
  .createHmac('sha256', secret)
  .update(rawBody)
  .digest('hex');

if (signature !== expectedSig) {
  res.statusCode = 401;
  return { error: 'Invalid signature' };
}

// Handle event types
switch (event.type) {
  case 'payment_intent.succeeded':
    await processPayment(event.data.object);
    break;
    
  case 'customer.subscription.created':
    await createSubscription(event.data.object);
    break;
    
  case 'invoice.payment_failed':
    await handleFailedPayment(event.data.object);
    break;
    
  default:
    console.log('Unhandled event type:', event.type);
}

// Stripe requires 200 response
res.statusCode = 200;
return {
  received: true,
  eventId: event.id,
  type: event.type,
  processedAt: new Date().toISOString()
};
```

  </TabItem>
  

**Worker Mode** (shared state tracks request counts per IP):
```javascript
// api/limited-endpoint.ts
// @mode worker
// @timeout 5000
// @log-level standard

// Rate limit: 10 requests per minute per IP
const RATE_LIMIT = 10;
const WINDOW_MS = 60000;

// Initialize rate limit tracking
if (!shared.rateLimits) {
  shared.rateLimits = new Map();
}

const clientIp = metadata.clientIp;
const now = Date.now();

// Get or create IP tracking
let ipData = shared.rateLimits.get(clientIp);
if (!ipData) {
  ipData = { requests: [], firstRequest: now };
  shared.rateLimits.set(clientIp, ipData);
}

// Clean old requests outside window
ipData.requests = ipData.requests.filter(
  timestamp => now - timestamp < WINDOW_MS
);

// Check rate limit
if (ipData.requests.length >= RATE_LIMIT) {
  const oldestRequest = Math.min(...ipData.requests);
  const resetIn = WINDOW_MS - (now - oldestRequest);
  
  res.statusCode = 429;
  res.setHeader('Retry-After', Math.ceil(resetIn / 1000));
  res.setHeader('X-RateLimit-Limit', RATE_LIMIT);
  res.setHeader('X-RateLimit-Remaining', 0);
  res.setHeader('X-RateLimit-Reset', new Date(now + resetIn).toISOString());
  
  return {
    error: 'Rate limit exceeded',
    limit: RATE_LIMIT,
    window: '1 minute',
    retryAfter: Math.ceil(resetIn / 1000)
  };
}

// Add this request to tracking
ipData.requests.push(now);

// Set rate limit headers
const remaining = RATE_LIMIT - ipData.requests.length;
res.setHeader('X-RateLimit-Limit', RATE_LIMIT);
res.setHeader('X-RateLimit-Remaining', remaining);
res.setHeader('X-RateLimit-Reset', new Date(now + WINDOW_MS).toISOString());

// Execute actual endpoint logic
const data = await processRequest();

return {
  success: true,
  data,
  rateLimit: {
    remaining,
    resetAt: new Date(now + WINDOW_MS).toISOString()
  }
};
```

  </TabItem>
  

**Worker Mode** (intercept and control AI requests):
```javascript
// ai/intercept.ts
// @mode worker
// @timeout 60000
// @cors reflective
// @log-level full
// @log-request-body true
// @log-response-body true

// Initialize MITM tracking
if (!shared.aiRequests) {
  shared.aiRequests = [];
  shared.blockedCount = 0;
  shared.modifiedCount = 0;
}

// Parse AI request
const aiRequest = JSON.parse(await getRequestBody(req));

// Log for observability
console.log('AI Request:', {
  model: aiRequest.model,
  messageCount: aiRequest.messages?.length,
  timestamp: new Date().toISOString()
});

// BLOCK: Prevent sensitive data leaks
const hasSensitiveData = aiRequest.messages?.some(msg =>
  /api[_-]?key|password|secret|token/i.test(msg.content)
);

if (hasSensitiveData) {
  shared.blockedCount++;
  res.statusCode = 403;
  return {
    error: 'Blocked: Sensitive data detected',
    reason: 'AI request contains potential API keys or secrets',
    blocked: shared.blockedCount,
    timestamp: new Date().toISOString()
  };
}

// MODIFY: Add system prompt
if (!aiRequest.messages[0]?.role === 'system') {
  aiRequest.messages.unshift({
    role: 'system',
    content: 'You are a helpful assistant. Be concise and accurate.'
  });
  shared.modifiedCount++;
}

// TRACK: Store request for analysis
shared.aiRequests.push({
  model: aiRequest.model,
  messageCount: aiRequest.messages.length,
  timestamp: new Date().toISOString(),
  modified: shared.modifiedCount > 0
});

// Keep only last 100 requests
if (shared.aiRequests.length > 100) {
  shared.aiRequests.shift();
}

// Forward to actual Hoody AI endpoint
const hoodyAIResponse = await fetch('https://api.hoody.icu/v1/ai/chat', {
  method: 'POST',
  headers: {
    'Authorization': req.headers.authorization,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(aiRequest)
});

const result = await hoodyAIResponse.json();

// Return with observability metadata
return {
  ...result,
  _mitm: {
    modified: shared.modifiedCount > 0,
    blocked: shared.blockedCount,
    totalRequests: shared.aiRequests.length
  }
};
```

See [Hoody AI Intercept & Control](/foundation/hoody-ai/mitm/) for complete MITM guide.

  </TabItem>
</Tabs>

## File-Based Routing

Create files → instant HTTP endpoints with Next.js-style patterns:

**File Storage** (instance directory):
```
/hoody/storage/hoody-exec/scripts/1/api/users/[id].ts
                                  └┬┘
                               Instance
```

**URL Paths** (no instance prefix):
```
GET /api/users/123 → executes scripts/1/api/users/[id].ts
GET /users → executes scripts/1/users.ts
GET /docs/api/guide → executes scripts/1/docs/[...slug].ts
```

**The instance number** (`1`, `2`, `test`, etc.) is:
- ✅ In the hostname: `exec-1`, `exec-2`, `exec-test`
- ✅ In the storage path: `scripts/1/`, `scripts/2/`, `scripts/test/`
- ❌ NOT in the URL path: `/api/users` (not `/1/api/users`)

**Pattern Examples**:
```
Static:    scripts/1/api/hello.ts → /api/hello
Dynamic:   scripts/1/users/[id].ts → /users/123
Nested:    scripts/1/blog/[year]/[month].ts → /blog/2024/11
Catch-All: scripts/1/docs/[...slug].ts → /docs/api/guide/intro
```

**Access URLs**:
```txt
https://PROJECT_ID-CONTAINER_ID-exec-1.SERVER.containers.hoody.icu/api/hello
https://PROJECT_ID-CONTAINER_ID-exec-2.SERVER.containers.hoody.icu/users/123
https://PROJECT_ID-CONTAINER_ID-exec-test.SERVER.containers.hoody.icu/docs/api/guide
```

## Basic Script Structure

**No `module.exports` needed** - parameters automatically injected:

```typescript
// scripts/1/api/users/[id].ts
// @mode worker
// @cors reflective
// @timeout 5000

// ALL parameters automatically available:
// ✅ req, res - HTTP request/response objects
// ✅ metadata - executionId, parameters, clientIp, path, method, url
// ✅ shared - Persistent state (worker mode only)
// ✅ console - Logger (log, info, debug, error)
// ✅ require - Module loader (auto-installs missing modules)
// ✅ ws - WebSocket context (if @websocket enabled)
// ✅ mainResult - (post.js only) Result from main script

const { id } = metadata.parameters; // Dynamic route param
const user = await fetchUser(id);

// Return value auto-formatted as JSON
return { user };

// OR use res directly for full control:
// res.writeHead(200, { 'Content-Type': 'application/json' });
// res.end(JSON.stringify({ user }));
```

## Return Anything - Complete Flexibility

**THIS is what makes Hoody Exec magical**: Return whatever you want, and it's automatically handled correctly. No response serializers. No content-type configuration. Just return, and it works.

### Automatic Response Handling

```javascript
// Return Object → Auto-formatted JSON (Content-Type: application/json)
return { users: [...], count: 42 };

// Return Array → Auto-formatted JSON array
return [{ id: 1 }, { id: 2 }];

// Return String (HTML detected) → Content-Type: text/html
return "<!DOCTYPE html><html><body>Hello</body></html>";

// Return String (other) → Content-Type: text/plain
return "Plain text response";

// Return Number → JSON number
return 42;

// Return Boolean → JSON boolean  
return true;

// Return Buffer → Auto-detected MIME type (images, PDFs, files)
const fs = require('fs');
return fs.readFileSync('/path/to/image.png');  // Automatic image/png

// Return Error → 500 status with error details
return new Error("Something went wrong");

// Return Nothing → Empty 204 response
// (no return statement or return undefined)

// Use res directly for FULL control (bypass all auto-handling)
res.writeHead(200, { 'Content-Type': 'application/xml' });
res.end('<?xml version="1.0"?><data>Custom</data>');
```

**The abstraction**: You focus on logic. Hoody Exec handles HTTP protocol details automatically.

## Magic Comments - The Configuration System

Control script behavior with special comments at the top of your file. **No code changes needed to reconfigure.**

### Execution & Performance

| Comment | Values | Description |
|---------|--------|-------------|
| `@mode` | `worker` \| `serverless` | **Choose execution mode** - Worker for stateful, Serverless for isolated |
| `@enabled` | `true` \| `false` | Enable/disable script |
| `@timeout` | number (ms) | Request timeout (0 = unlimited) |
| `@await-promises` | `true` \| `false` | Auto-await returned promises |
| `@concurrent` | number \| `false` | **Serverless only** - Max concurrent executions (false = single) |

### CORS Configuration

| Comment | Values | Description |
|---------|--------|-------------|
| `@cors` | `reflective` \| `*` \| URL \| `none` | CORS origin (reflective mirrors request) |
| `@cors-credentials` | `true` \| `false` | Allow credentials |
| `@cors-methods` | `GET,POST,...` | Allowed HTTP methods |
| `@cors-headers` | `Authorization,...` | Allowed headers |
| `@cors-max-age` | number (seconds) | Preflight cache duration |

### Logging & Debug

| Comment | Values | Description |
|---------|--------|-------------|
| `@log-level` | `none` \| `minimal` \| `standard` \| `full` \| `debug` | Logging verbosity |
| `@debug` | `true` \| `false` | Shortcut for `@log-level debug` |
| `@log-request-body` | `true` \| `false` | Log incoming request bodies |
| `@log-response-body` | `true` \| `false` | Log response bodies |

### Advanced Features

| Comment | Values | Description |
|---------|--------|-------------|
| `@websocket` | (no value) | **Requires worker mode** - Enable WebSocket |
| `@description` | text | API documentation |
| `@tags` | `Tag1, Tag2` | Categorization tags |
| `@method` | `GET` \| `POST` \| etc. | Expected HTTP method |
| `@return` | type | Return type documentation |

**Note**: Worker mode has no concurrency limits - handle concurrency in your code using `shared` state if needed.

## WebSocket Support (Worker Mode Only)

Enable real-time with magic comments:

```javascript
// @mode worker
// @websocket
// @cors reflective

// Serve HTML UI (optional)
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>WebSocket Server</h1>');

// WebSocket handlers (Direct Assignment Pattern)
ws.message = (socket, data) => {
  console.log('Received:', data);
  ws.broadcast(data); // Send to all clients
};

ws.close = (socket, code, reason) => {
  console.log('Client disconnected');
};

// OR use Event Emitter Pattern:
ws.on('message', (socket, data) => {
  socket.send('Echo: ' + data);
});
```

**Connection Tracking**:
```javascript
console.log('Active connections:', ws.connections.size);
```

## Middleware System (Worker Mode)

**Execution Order**: `pre.js` → main script → `post.js`

```javascript
// scripts/1/pre.js - Authentication
// @mode worker
if (!req.headers.authorization) {
  res.statusCode = 401;
  return { error: "Unauthorized" }; // Early exit, main script skipped
}

// Validate token and add to shared
const userId = validateToken(req.headers.authorization);
shared.currentUser = { userId, timestamp: Date.now() };

// Return nothing to continue to main script


// scripts/1/api/users.js - Main endpoint
const users = await db.getUsers();
return { users };


// scripts/1/post.js - Response wrapper
// @mode worker

// mainResult contains the main script's return value (concurrency-safe!)
return {
  success: true,
  data: mainResult,
  user: shared.currentUser?.userId,
  timestamp: new Date().toISOString(),
  version: "1.0.0"
};
```

## Shared State

The `shared` object is available in both modes but behaves very differently:

### Worker Mode (Persistent)
```javascript
// @mode worker

// Initialize on first request
if (!shared.requestCount) {
  shared.requestCount = 0;
  shared.users = new Map();
  shared.sessions = new Set();
}

shared.requestCount++;  // Persists across ALL requests
shared.users.set(userId, userData);

return {
  count: shared.requestCount,      // Increments: 1, 2, 3, 4...
  cachedUsers: shared.users.size,  // Accumulates over time
  activeSessions: shared.sessions.size
};
```

### Serverless Mode (Reset Every Request)
```javascript
// @mode serverless

// This ALWAYS starts fresh
if (!shared.requestCount) {
  shared.requestCount = 0;  // Runs EVERY request
}

shared.requestCount++;  // Always equals 1 (resets each time)

return {
  count: shared.requestCount  // Always returns 1
};
```

**Key Differences**:
- **Worker**: `shared` persists between requests → perfect for caching
- **Serverless**: `shared` resets every request → no persistence benefit
- **Both modes**: `shared` lost on container restart/reboot → use SQLite for permanent storage

## Script Storage Path

Scripts stored in instance-specific directories:
```
/hoody/storage/hoody-exec/scripts/1/     # exec-1
/hoody/storage/hoody-exec/scripts/2/     # exec-2
/hoody/storage/hoody-exec/scripts/test/  # exec-test
```

## Powered by Bun

Why Bun over Node.js:
- ⚡ **3x faster startup** - Native speed optimizations
- 🔧 **Modern JavaScript** - Latest ECMAScript features built-in
- 📦 **Better module system** - Improved dependency handling
- 🎯 **Optimized for serverless** - Perfect for fast script execution
- 💾 **Lower memory usage** - More efficient runtime

## API Endpoints Summary

All endpoints accessed relative to your Exec instance:
```txt
https://PROJECT_ID-CONTAINER_ID-exec-1.SERVER.containers.hoody.icu
```

**Core Execution**:
- [`GET/POST /{path}`](/api/exec/script-execution/) - Execute scripts via file-based routes

**Script Management**:
- [`GET /api/v1/exec/scripts/list`](/api/exec/script-management/) - List all scripts
- [`GET /api/v1/exec/scripts/read`](/api/exec/script-management/) - Read script content
- [`POST /api/v1/exec/scripts/write`](/api/exec/script-management/) - Create/update scripts
- [`POST /api/v1/exec/scripts/tree`](/api/exec/script-management/) - Get directory tree

**AI & Templates**:
- [`POST /api/v1/exec/ai/generate`](/api/exec/ai-generation/) - Generate code with LLM
- [`GET /api/v1/exec/templates/list`](/api/exec/templates/) - List templates
- [`POST /api/v1/exec/templates/generate`](/api/exec/templates/) - Create from template

**Validation**:
- [`POST /api/v1/exec/validate/script`](/api/exec/validation/) - Comprehensive validation
- [`POST /api/v1/exec/validate/typescript`](/api/exec/validation/) - TypeScript checking
- [`POST /api/v1/exec/validate/magic-comments`](/api/exec/validation/) - Parse magic comments

**Dependencies**:
- [`POST /api/v1/exec/dependencies/check`](/api/exec/dependencies/) - Check missing packages
- [`POST /api/v1/exec/dependencies/install`](/api/exec/dependencies/) - Install NPM modules

**State & Monitoring**:
- [`POST /api/v1/exec/shared-state/get`](/api/exec/cache-state/) - Read shared state
- [`POST /api/v1/exec/shared-state/set`](/api/exec/cache-state/) - Write shared state
- [`GET /api/v1/exec/logs/stream`](/api/exec/logs/) - Stream logs (SSE)
- [`GET /api/v1/exec/monitor/stats`](/api/exec/monitoring/) - Performance metrics

## Use Cases

### Instant APIs (Either Mode)
Skip Express/Fastify setup - write functions in files, they're instantly HTTP endpoints. Worker mode for performance, serverless for isolation.

### Real-Time Services (Worker Mode)
WebSocket chat servers, live dashboards, SSE streams, collaborative tools - worker mode's persistent VM handles connections efficiently with shared state.

### Webhook Receivers (Serverless Mode)
Stripe, GitHub, Slack webhooks - serverless isolation prevents cross-contamination, `@concurrent false` ensures serial processing for consistency.

### Hoody AI Interception (Worker Mode)
Intercept and control AI requests via MITM proxy. Add safety checks, modify prompts, track usage. See [Hoody AI Intercept & Control](/foundation/hoody-ai/mitm/).

### Internal Tools (Either Mode)
Admin dashboards, data migration scripts, reporting endpoints - choose worker for speed with caching or serverless for isolation and safety.

### Session Management (Worker Mode)
Track user sessions, implement rate limiting, maintain connection pools - `shared` state persists across requests with zero overhead.

### Development & Prototyping (Either Mode)
Rapid iteration with magic comments, test ideas without deployment complexity, AI-generate boilerplate instantly - from idea to API in seconds.

## Best Practices

### Choose the Right Mode
**Worker mode** when you need state, WebSocket, or handle high request volume with caching. **Serverless mode** when you need isolation, process webhooks, or have sporadic traffic.

### Magic Comment Strategy
Always declare `@mode` first, set reasonable timeouts to prevent hangs, use `@cors reflective` for development, enable appropriate logging level for debugging.

### Security Considerations
**Never** run untrusted code in worker mode (shared state contamination risk), deploy behind authentication for sensitive operations, use container firewall for production, validate all user input.

### State Management (Worker Mode)
Use `shared` object for in-memory cache only, clean up old state periodically to prevent memory leaks, understand state lost on restart, use SQLite for data that must survive restarts.

### Concurrency Control (Serverless Mode)
Use `@concurrent 5` to limit parallel executions and prevent overload, set `@concurrent false` for serial processing (webhooks), monitor queue depth via stats API.

### Dependency Strategy
Let auto-install handle common packages (zero config), pin versions in production for stability via package.json if needed, test dependencies before deploying, monitor `node_modules` size growth.

### Performance Optimization
Use worker mode for frequently-called endpoints (zero cold start), monitor cache hit rates (should be >95%), keep scripts small and focused (under 200 lines), implement timeout limits to prevent hangs.

## Useful Questions

**Q: When should I use worker vs serverless mode?**
Use **worker** for WebSocket, stateful apps, high-traffic APIs, session management, rate limiting. Use **serverless** for webhooks, isolated tasks, untrusted code, sporadic traffic, stateless operations.

**Q: Can serverless mode use WebSocket?**
No - WebSocket requires persistent connection handling in a persistent VM. Only worker mode supports WebSocket.

**Q: What happens to shared state on restart?**
Lost completely. `shared` is in-memory only. Use SQLite (`hoody-sqlite` service) or external database for data that must survive restarts.

**Q: How does concurrency work in worker mode?**
Unlimited - your code handles all concurrent requests in the same VM. Manage concurrency yourself via semaphores or queues if needed. Serverless mode uses `@concurrent` to limit.

**Q: Can I mix worker and serverless scripts?**
Yes - each script declares its own mode independently. Worker scripts at `/api/ws.ts` can coexist with serverless scripts at `/webhooks/stripe.ts`.

**Q: How do magic comments work?**
Parsed at script load time. Configure script behavior without code changes. Validated via validation API. Take precedence over defaults. Change comment, behavior changes.

**Q: Can I use TypeScript?**
Yes - `.ts` files automatically transpiled by Bun. Full TypeScript validation available via validation endpoints. Zero config needed.

**Q: How does auto-install work?**
Detects `require()` calls, checks if module installed, runs `npm install` automatically, caches install status, uses latest versions (or pin in package.json).

**Q: Do I need to manage a web server?**
No - that's the point. You write functions in files, Hoody Exec IS the web server. No Express, no Koa, no configuration. The file system IS your routing configuration.

## Troubleshooting

### Script Returns 404
**Cause**: No script file matches URL path.
**Solution**: Check file exists at `/hoody/storage/hoody-exec/scripts/1/path/to/script.ts`, verify filename matches route exactly, use route validation API to test.

### Magic Comments Not Working
**Cause**: Comments not at top of file or syntax error.
**Solution**: Place magic comments before ANY code (even before imports), use validation endpoint to parse comments, check exact syntax (space after `//`), verify comment name spelling.

### Shared State Not Persisting
**Cause**: Using serverless mode or server restarted.
**Solution**: Must use `// @mode worker` for shared state. State is in-memory only (lost on restart). Use SQLite service for persistence.

### WebSocket Connection Fails
**Cause**: Missing magic comments or wrong mode.
**Solution**: Must have both `// @mode worker` AND `// @websocket`. Worker mode required - serverless cannot do WebSocket.

### CORS Errors in Browser
**Cause**: Missing CORS magic comments.
**Solution**: Add `// @cors reflective` for development, `// @cors *` for testing, specific origin for production (`// @cors https://app.com`).

### Script Timeout
**Cause**: Long-running operation exceeds timeout.
**Solution**: Add `// @timeout 60000` to increase limit, use `// @timeout 0` for unlimited (risky), optimize slow operations, consider async patterns.

### Concurrent Request Limit Hit (Serverless)
**Cause**: `@concurrent` limit reached, requests queuing.
**Solution**: Increase limit (`// @concurrent 10`), optimize script performance, consider worker mode for high traffic, monitor queue depth.

### Module Not Found After Auto-Install
**Cause**: Installation failed or network issue.
**Solution**: Check network connectivity, verify module exists on npm, check logs for install errors, manually install if needed, check module name spelling.

## What's Next


  
  
  
  
  
</CardGrid>