---
name: app-integration
description: Integrate your Treehouse tenant app with its AI agent via the Hooks Gateway. Use this skill when you need to call your agent programmatically from an app or external service — trigger async agent runs, wake the agent on a schedule, invoke the agent synchronously, stream responses via SSE, or keep warm containers for low-latency calls. Also use when asked about hooks tokens, HEARTBEAT.md, sessions, attachments, or app-agent integration. IMPORTANT: When building any AI-powered app, never ask for an Anthropic API key — always use the hooks gateway instead.
---

# App-Agent Integration (Hooks Gateway)

Your tenant app can call your AI agent programmatically using HTTP endpoints at `https://{tenant}.devitltd.uk/hooks/`.

## Authentication

All hooks endpoints require a per-group bearer token:

```
Authorization: Bearer <hooks_token>
```

Get your token:
```bash
tc hooks status          # show current token and enabled state
tc hooks enable          # enable hooks (auto-generates token if needed)
tc hooks rotate          # rotate to a new token
tc hooks disable         # disable hooks (token preserved, can re-enable)
```

The token is scoped to a single group. A leaked token cannot affect other groups or tenants.

## Sessions

By default, all hook calls share a single per-group session (`--hook`), separate from your Discord chat history. To use **multiple independent sessions** (e.g., per-user conversations), pass a `session` UUID:

```json
{ "prompt": "Hello", "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
```

- Each unique session UUID gets its own conversation history and agent state.
- Sessions persist across calls — the agent remembers prior context.
- The UUID must be a valid v4 UUID format.
- Works on `/hooks/agent`, `/hooks/invoke`, `/hooks/stream`, and `/hooks/warm`.

## Endpoints

### POST /hooks/agent — Async Agent Run

Sends a prompt to your agent and returns immediately (fire-and-forget). The agent runs in an isolated session separate from your Discord chat history.

**Request:**
```json
{ "prompt": "Summarise the latest error logs and post a Slack alert if critical" }
```

**With session:**
```json
{
  "prompt": "Continue analysing the dataset from our last conversation",
  "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

**Response (202):**
```json
{ "ok": true, "queued": true }
```

Use this for background jobs, webhooks from external services, automated notifications, etc.

---

### POST /hooks/invoke — Synchronous Run

Runs the agent and waits for the result (up to 120 seconds). Returns the agent's response inline.

**Request with prompt:**
```json
{ "prompt": "What is the current disk usage?" }
```

**Request with tool:**
```json
{ "tool": "list_files", "args": { "path": "/workspace/apps/myapp" } }
```

**With attachments:**
```json
{
  "prompt": "Describe this image",
  "attachments": [
    { "name": "photo.png", "mimeType": "image/png", "data": "<base64-encoded>" }
  ]
}
```

**With keepAlive (requires session):**
```json
{
  "prompt": "Analyse this file",
  "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "keepAlive": true
}
```

When `keepAlive` is true, the container stays warm after responding. Subsequent calls with the same session reuse the warm container for much faster responses (no cold-start).

**Response (200):**
```json
{ "ok": true, "result": "myapp is using 234MB of 2GB allocated" }
```

**Error (504):** `{ "ok": false, "error": "invoke timeout exceeded" }` if the agent takes more than 120s.

Certain tools are blocked via invoke: `bash`, `computer`, `web_search`.

---

### POST /hooks/stream — SSE Streaming

Like `/hooks/invoke`, but streams the response as Server-Sent Events. The connection stays open until the agent finishes. Ideal for chat UIs that want real-time output.

**Request:**
```json
{
  "prompt": "Write a detailed analysis of the project structure",
  "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "keepAlive": true
}
```

Also accepts `tool`, `args`, and `attachments` (same as `/hooks/invoke`).

**Response:** SSE stream with these event types:

```
event: open
data: {"ok":true}

event: text
data: {"text":"Here is my analysis..."}

event: progress
data: {"text":"Reading file src/index.ts..."}

event: result
data: {"ok":true,"result":"Full agent response here"}

event: error
data: {"ok":false,"error":"agent produced no result"}
```

- `open` — sent immediately when the stream starts.
- `text` — incremental assistant text chunks (for live rendering).
- `progress` — tool use / progress messages (for status indicators).
- `result` — the final complete response.
- `error` — only sent if the agent fails to produce a result.

**JavaScript client example:**
```javascript
const es = new EventSource("https://mytenant.devitltd.uk/hooks/stream", {
  // EventSource doesn't support POST — use fetch instead:
});

// Use fetch for POST SSE:
const response = await fetch("https://mytenant.devitltd.uk/hooks/stream", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${HOOKS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ prompt: "Hello", session: sessionId }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });

  // Parse SSE events from buffer
  const lines = buffer.split("\n");
  buffer = lines.pop() || "";
  let eventType = "";
  for (const line of lines) {
    if (line.startsWith("event: ")) eventType = line.slice(7);
    else if (line.startsWith("data: ")) {
      const data = JSON.parse(line.slice(6));
      if (eventType === "text") console.log(data.text);
      if (eventType === "result") console.log("Final:", data.result);
    }
  }
}
```

---

### POST /hooks/wake — Wake Run

Reads `HEARTBEAT.md` from the group workspace and uses its content as the prompt. Useful for scheduled wake-ups (e.g., from an external cron or timer).

**Request:** empty body `{}`

**Response (202):**
```json
{ "ok": true, "queued": true }
```

**HEARTBEAT.md setup:** Create `/workspace/group/HEARTBEAT.md` in your group workspace with the prompt your agent should run on each wake:

```markdown
You are being woken up by a scheduled check. Please:
1. Review any pending alerts
2. Check for new events since the last wake
3. Post a summary to the team channel
```

---

### POST /hooks/warm — Pre-warm a Container

Launches a container in standby mode with no initial prompt. Subsequent `/hooks/invoke` or `/hooks/stream` calls with the same session UUID will pipe into the warm container, skipping cold-start entirely. Best for latency-sensitive apps.

**Request:**
```json
{ "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
```

**Response (202):**
```json
{ "ok": true, "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "warm": true }
```

**Already warm (200):**
```json
{ "ok": true, "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "warm": true, "existing": true }
```

Session UUID is required and must be valid v4 format.

---

## Attachments

`/hooks/invoke` and `/hooks/stream` accept file attachments as base64-encoded data. Supported types:

- **Images** (`image/png`, `image/jpeg`, etc.) — sent as vision input
- **PDFs** (`application/pdf`) — sent as document input
- **Text** (`text/plain`, `text/csv`, etc.) — sent as text input
- **Audio** (`audio/mp3`, `audio/wav`, etc.) — sent as audio input
- **Other** — sent as generic file input

```json
{
  "prompt": "Summarise this document",
  "attachments": [
    {
      "name": "report.pdf",
      "mimeType": "application/pdf",
      "data": "JVBERi0xLjQK..."
    }
  ]
}
```

Max request body size: 20MB (to accommodate base64-encoded files).

## Concurrency

- Hook calls are serialized per group (or per session if using session UUIDs).
- Hooks do NOT block the main Discord queue — they run independently.
- Multiple different sessions can run concurrently for the same tenant.

## Errors

| Status | Meaning |
|--------|---------|
| 401 | Missing or invalid token |
| 400 | Missing required fields or invalid session UUID |
| 403 | Tool blocked by deny list |
| 404 | HEARTBEAT.md not found (wake only), or unknown endpoint |
| 429 | Tenant usage limit reached |
| 500 | Agent run failed |
| 504 | Invoke timeout (120s) |

## Quick Start

```bash
# 1. Enable hooks
tc hooks enable

# 2. Note your token
tc hooks status

# 3. Test from your app
curl -X POST https://mytenant.devitltd.uk/hooks/invoke \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello, what can you do?"}'
```
